TokyoWesterns CTF 2020 WEB WP

四个WEB,就做出两个,tcl...

比赛网址:https://score.ctf.westerns.tokyo/

截止现在还有 30 多个小时结束,哥哥们有时间可以去康康

urlcheck v1

题目附赠源码,是个Py写的网站

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 '🤔'
        url = r.headers.get('location')
        if valid_ip(urlparse(url).netloc) == False:
            return '🤔'
        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 '🥺'
    return app.flag

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_ip(urlparse(url).netloc) == False:
        return '🥺'
    return get(url)

@app.route('/')
def index():
    return '''
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app" class="container">
      <h1>urlcheck v1</h1>
      <div class="input-group input-group-lg mb-3">
        <input v-model="url" placeholder="e.g.) http://11.45.148.93/" class="form-control">
        <div class="input-group-append">
          <button v-on:click="checkStatus" class="btn btn-primary">check</button>
        </div>
      </div>
      <div v-if="status" class="alert alert-info">{{ d(status) }}</div>
    </div>
    <script>
      new Vue({
        el: '#app',
        data: {url: '', status: ''},
        methods: {
          d: function (s) {
            let t = document.createElement('textarea')
            t.innerHTML = s
            return t.value
          },
          checkStatus: function () {
            fetch('/check-status?url=' + this.url)
              .then((r) => r.text())
              .then((r) => {
                this.status = r
              })
          }
        }
      })
    </script>
</body>
</html>
'''

源码大概意思是,只允许输入以点分割的IP,且如果输入127.0.0.1这种IP ,题目会返回false

当我们能够利用SSRF访问到本地路由 /admin-status便会得到flag

本想用一下 302 跳转,发现它会递归遍历你 Location 的位置(见get函数),那就试试其它方法吧

试了一会,发现八进制IP可行

payload:http://0177.000.000.001/admin-status

urlcheck v2

同样题目给了源码

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 '🤔'
        url = r.headers.get('location')
        if valid_fqdn(urlparse(url).netloc) == False:
            return '🤔'
        return get(url, recursive_count + 1)
    return a+r.text

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

@app.route('/check-status')
def check_status():
    url = flask.request.args.get('url', '')
    if valid_fqdn(urlparse(url).netloc) == False:
        return '🥺'
    return get(url)

@app.route('/')
def index():
    return '''
<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="app" class="container">
      <h1>urlcheck v2</h1>
      <div class="input-group input-group-lg mb-3">
        <input v-model="url" placeholder="e.g.) http://westerns.tokyo/" class="form-control">
        <div class="input-group-append">
          <button v-on:click="checkStatus" class="btn btn-primary">check</button>
        </div>
      </div>
      <div v-if="status" class="alert alert-info">{{ d(status) }}</div>
    </div>
    <script>
      new Vue({
        el: '#app',
        data: {url: '', status: ''},
        methods: {
          d: function (s) {
            let t = document.createElement('textarea')
            t.innerHTML = s
            return t.value
          },
          checkStatus: function () {
            fetch('/check-status?url=' + this.url)
              .then((r) => r.text())
              .then((r) => {
                this.status = r
              })
          }
        }
      })
    </script>
</body>
</html>
'''

试了试进制、IPv6等,貌似都不行,一度陷入僵局....

这时注意到这道题貌似支持 domain 的解析(自定义函数valid_fqdn使用了socket.gethostbyname()),联想上一题只支持纯数字 IP,猜测这里可能有问题

SSRF+和域名有关,自然而然的想到了DNS Rebinding,那就试试吧

有个小坑点,卡了半下午

因为这道题不支持 ceye.io 域名的解析,所以后面我想的是配置个人域名解析,但个人域名,如阿里云、腾讯云的 TTL 最短都高达600,所以在群里问了问,最后一个学长给了个配好了的域名7f000001.0b2d945d.rbndr.us

多访问几次即可拿到flag

image-20200919185738846

看了看后面的两个题,大概都是这种

image-20200919190150708

太菜了,连文件都看不懂,溜了溜了

(有做出来的哥哥记得ddw)

此条目发表在DNS Rebinding, SSRF分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注