KCon 议题解读 | Python动态代码审计

 

动态代码审计的用处

  1. 大型项目代码结构复杂
  2. 有些危险的功能隐藏较深(危险的定时计划任务、sqlite数据库任意创建导致任意文件覆盖……)
  3. 提高效率,希望通过一些黑盒的方法比较快速的找到漏洞。

 

常见漏洞分类

  1. 数据库操作
  2. 敏感函数的调用和传参
  3. 文件读写操作
  4. 网络访问操作

 

正文目录

  1. 数据库general log 日志
  2. hook关键函数
  3. 结合auditd
  4. http盲攻击
  5. fuzzing

 

数据库日志

general-log是记录所有的操作日志,不过他会耗费数据库5%-10%的性能,所以一般没什么特别需要,大多数情况是不开的,例如一些sql审计和代码审计等,那就是打开来使用了

Mysql通过命令行的方式打开general log:

set global general_log_file='';
set global general_log=on;

Postgresql 通过编辑配置文件打开general log:

编辑:postgresql.conf

log_directory = 'pg_log'                    
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_statement = 'all'

打开之后用burp向web api发送一些包含sql注入的畸形数据

利用Linux的grep指令过滤出sql执行中的ERROR关键字,就可以很快的找到sql注入漏洞

 

Hook关键函数

根据基于python的自动化代码审计pyekaboo这两个文章的启发而来

Python对象可以被轻易的改变,通过设置PYTHONPATH这个环境变量,使python在加载string这个模块的时候优先加载我自定义的string模块,下图演示的是劫持string模块的upper函数

之后我的思路是,通过劫持python的函数,把输入到危险函数的参数输出到日志中,最后调用正常的python函数

这样就可以劫持我们认为敏感的函数

劫持模块的demo:

import imp
import sys
class _InstallFcnHook(object):
    def __init__(self,fcn):
        self._fcn=fcn

    def _pre_hook(self,*args,**kwargs):
        print "hook:"+str(args)
        return (args,kwargs)
    def __call__(self,*args,**kwargs):
        (_hook_args,_hook_kwargs)=self._pre_hook(*args,**kwargs)
        retval=self._fcn(*_hook_args,**_hook_kwargs)
        return retval

fd,pathname,desc=imp.find_module(__name__,sys.path[::-1])
mod =imp.load_module(__name__,fd,pathname,desc)

system=_InstallFcnHook(system)

劫持效果:

这就意味着我们可以劫持危险的函数,把参数输出到日志里面,通过日志信息,可以判断这些参数是否可以被输入控制。通过这种方式可以方便找到ssti、pickle反序列化漏洞和命令执行漏洞等其他的漏洞

而且这些可以很方面的拓展到其他的模块中

  1. cd hook/ #进入到hook模块所在目录
  2. cp os.py xxx.py #把os模块复制一份,xxx模块是你想hook的模块
  3. 编辑xxx.py :注释掉原来被hook的函数,添加想要hook的函数,下面的示例是hook了subprocess模块中check_call函数

Ps:需要填一些坑:

  1. 修改启动代码从shell中启动python web
    因为有一些python web是从wsgi中启动的,这些只要简单修改启动代码就可以从WSGI方式启动切换到shell启动
  2. 从内存中删掉已加载的模块
    一些模块通过import动态导入,需要在动态导入后通过del modules删掉被装载的模块
  3. 关闭调试选项
    例如在flask启动时将debug选项设置为false,否则会产生两个python进程
  4. 其他问题
    Python web性能下降、代码不兼容、有些模块无法被hook,其中python的内置函数open就无法通过这样的方式被hook。

 

结合Auditd

网上有很多方法去获取python web的文件读写操作,其中有一种是在文件读写函数前面添加装饰器,但是我觉得那种方法太过于烦琐,且不能覆盖到所有的文件读写操作,那怎么不通过修改原始代码去获取文件读写操作?

可以利用Auditd:

auditd(或 auditd 守护进程)是Linux审计系统中用户空间的一个组件,其可以记录Linux中文件,进程等操作,且安装方便

CentOS 默认安装

Ubuntu 安装:apt-get install auditd

只要简单的配置就可以监视一些文件操作

sudo auditctl -a exclude,always -F msgtype!=PATH -F msgtype!=SYSCALL    #记录文件操作
sudo auditctl -a always,exit -F arch=b64 -S execve -k rule01_exec_command  #记录所有的shell指令的执行
sudo auditctl -a always,exit -F pid=$mypid    #记录指定进程文件操作

执行 sudo auditctl -l 查看所有的规则

只要发送一些包含目录跳转的畸形数据给webapi,如有任意文件读取的漏洞就可以很快的发现。
因为Auditd日志中包含大量其他的日志,通过grep和关键字高亮工具(https://github.com/paoloantinori/hhighlighter)进行查看日志(grep过滤PATH,高亮name)

除了记录文件读取,还能记录文件的其他操作

这样就可以找到:

  1. 任意文件上传
  2. 任意文件创建
  3. 任意文件读取
  4. 任意文件删除

 

http盲攻击

怎么解决诸如ssrf等网络操作的问题?

ssrf攻击可以让黑客越过防火墙来探测或者攻击一个大型企业的内网

那么在动态代码审计过程中就可以通过构造特定的poc,来探测代码中是否有对应的漏洞

可以构造请求dns解析的数据

1.命令执行的常见形式

Ping –c 1 xxx.pw

2.ssrf的常见形式

3.xxe的常见形式

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xdsec [
<!ELEMENT methodname ANY >
<!ENTITY xxe SYSTEM "http://xxxx.pw/text.txt" >]>
<methodcall>
<methodname>&xxe;</methodname>
</methodcall>

通过dns日志的记录可以很快找到诸如ssrf、xxe、命令执行等,包括可以找到一些隐藏的比较深的定时计划任务的中漏洞

 

fuzzing

做完上面的一些工作之后,我在想如何提高我的工作效率,毕竟我们部门产品众多,但是代码审计这个工作只有我一个在做。我把正常数据,畸形数据,poc数据,等其他数据全发给web api,然后在审计数据库日志,危险函数日志,auditd日志,dns日志,web报错日志,希望能从日志中发现潜藏的漏洞

利用burp的intruder这个功能就可以实现,测试用例可以使用wooyun提供的fuzzing数据

Ps:还是有几个自己需要处理的问题

  1. 需要根据自己的业务类型制定自己的测试用例
  2. 自己要想办法处理产生的大量的日志
  3. 其他问题

 

未来要做的事情

  1. 自动化部署客户端
  2. 开发一个日志处理平台
  3. 尽可能的覆盖更多的漏洞类型
  4. 丰富测试用例
  5. 开源 ( https://github.com/niexinming/python_hook)
(完)