python 测试app漏洞_Python代码审计实例三则

94ce769232d4a39caa5347e0ed132c3e.gif

目录

一、SQL盲注
二、身份认证逻辑
三、条件竞争

突然发现,笔者接触代码审计工作也有一年半的时间了,经历的大大小小的代码审计项目百余项,但是并没有好好的研究过关于python的代码审计内容,或者说python有什么需要审计的呢?fortify拓展插件貌似也支持python审计,至少原版我没成功过。

但,前不久一个小伙伴还是问了我python代码审计的问题,code终究还是code,万变不离其宗,今天笔者借朋友之前写的几个小实例抛砖引玉下,简单涉及下这一冷门领域。

67d598208b1baa5e2318dad5f6a351c2.png

一、SQL盲注

sql注入这个东西吧,稍微有点安全开发的常识就不会出现,我们简单来看下python中的sql注入是什么个样子。
这是一个查询系统的马卡龙配色查询表单:

e3da59a6375ea92b622662a4bddef383.png

<form id="searchForm" method="post">
<fieldset>
<input id="s" type="text" name="username" placeholder="请输入要查询的内容"/>
<input type="submit" value="查询" id="submitButton"/>

<div id="searchInContainer">
<input type="radio" name="searchmode" value="MeekSearch" id="searchSite" checked />
<label for="searchSite" id="siteNameLabel">标准模式label>

<input type="radio" name="searchmode" value="WildSearch" id="searchWeb" />
<label for="searchWeb">狂野模式label>
div>
fieldset>
form>

这是传入后端的post表单。
当我尝试进行黑盒SQL注入测试的时候,前端返回了500的错误,这个时候从事黑盒渗透的小伙伴们会有什么想法?放弃or继续?

3ff1ad1c46ca99683c51d55f4dd2abc0.png

我们来看下后台debug:

当前查询内容为:rabbit'
MySQLdb._exceptions.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for t
he right syntax to use near ''' at line 1")

唉,我这暴脾气,这种绝对意义上的盲注,sqlmap尝试了两次败下阵来,对于黑盒测试工作来讲,差不多可以去摸鱼了。

然而,对于一个白盒来讲,我们必须相信眼前看到的!
顺着username这个参数几经周折,我们终于找到了它的调用:

sql = ("SELECT weight FROM `regmeek` WHERE username={}".format(username))
mycursor.execute(sql)

涉及到了数据库查询中一个权重的字段,这个字段并不会反馈给前端,仅用于后端权重记录,所以造成了这个SQL盲注,而且因为返回结果报错,导致最终服务器并未做出正确响应,返回500报错,某种意义上讲,就算是漏洞也完全无法利用。

然而OOB(数据外带攻击)正是为此而生,通过DNSlog外带,依然可以拿到注入结果,时间关系不做展开,有兴趣的小伙伴可回顾之前的SQL注入OOB教程。

最后,来说下python的SQL注入修复方案,大同小异:
1、使用ORM,即尽可能不要直接拼接SQL语句。
2、验证输入类型和输入内容,即filter过滤器。
3、转义特殊字符,如引号、分号等。
4、参数化查询,各种接口库为我们自动转义。

笔者的防sql注入习惯正是参数化查询,我们在此给出修复后的code:

sql = ("SELECT weight FROM `regmeek` WHERE username=%s")
sqlV=[username]
mycursor.execute(sql,sqlV)

大家可以跟上面的漏洞代码简单做下对比,当然同样推荐ORM,有机会给大家介绍,emmmm,放到Scrapy章节吧,简单预告下。

二、身份认证逻辑

逻辑漏洞是最为有趣的漏洞,基本无法为漏扫软件所发现,这就意味着小白帽需要更加细心一些,我们本次以一个flask的小demo来错误示范下。
我们来简单看下这货的身份验证逻辑:

app.config['SECRET_KEY'] = 'rabbitmask'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.session_protection='strong'
#session_protection 能够更好的防止恶意用户篡改 cookies, 当发现 cookies 被篡改时, 该用户的 session 对象会被立即删除, 导致强制重新登录.
login_manager.login_view = 'login'
#指定了登录页面的视图函数
login_manager.login_message = 'Welcome To AboutU!'
#指定了提供用户登录的提示信息

@login_manager.user_loader
def load_user(username):
if userget(username) is not None:
curr_user = UserMixin()
curr_user.id = username
return curr_user

可以看到也是做足了细节,所有系统内界面均做足了权限认证,基本不存在未授权访问等问题。

82fc1b5c7104a3ce5ba5e9c9ee1c4039.png

然而有个set-cookie细节引起了我的注意:

if usercheck(username,password):
curr_user = UserMixin()
curr_user.id = username
login_user(curr_user)
resp = make_response(render_template('AboutU.html',username=request.cookies.get('username')))
resp.set_cookie('username', username)
return resp

请求:

POST /login HTTP/1.1
Host: 192.168.1.254:1988
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Connection: close
Referer: http://192.168.1.254:1988/
Upgrade-Insecure-Requests: 1

username=admin&password=admin

响应:Set-Cookie: username=admin;

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1484
Set-Cookie: username=admin; Path=/
Set-Cookie: session=.eJwlzrsRgzAMANBdVFPItrAsluH084UiKSBUueyeIm-C94F9nnk9YHufdy6wHwEbMFImVVQfbJph2FsxqlwaU9gsNafM1RtrDGfuTRllcJtrzRwi3ZOrhHDn7LUEOmKihgW7UYZ6k1WmdQwcSk6dCHGkK1rhAgvcV57_jMbzeMH3B272MRE.XZFsPg.Of_mJb4c4nhcBBRwjIqouJFSo4U; HttpOnly; Path=/
Server: Werkzeug/0.11.15 Python/3.7.2
Date: Mon, 30 Sep 2019 02:45:18 GMT

我们再来看下管理后台的身份验证,跟前端不同的地方在于通过isadmin()判断是否为管理员然后usercheck()判断身份,这些都没问题,还有一处差异是set-cookie多附了一个值:resp.set_cookie('isadmin', '1')

if isadmin(username) and usercheck(username,password):
curr_user = UserMixin()
curr_user.id = username
login_user(curr_user)
re = usermanager.usersearchall()
resp = make_response(render_template('adminconsole.html',usersearch=re))
resp.set_cookie('username', username)
resp.set_cookie('isadmin', '1')
return resp

我们看下系统对于越权的验证机制:

if request.cookies.get('isadmin')=='1':
re = usermanager.usersearchall()
return render_template('adminconsole.html',usersearch=re)
else:
abort(403)

/摊手手,这就凉了呀,对于权限的把控居然依赖的是cookie中的isadmin参数,的确,对于黑盒测试的普通用户来讲,是完全察觉不到这个参数的,但白盒自然了如指掌了。
登录前台访问后台返回403:

22924e4a290d24a89071c02448ea3bea.png

然而我们在cookie中手动加入isadmin参数,身份认证绕过成功:

53b8aefcafff4f3cc69eea1a1835208e.png

修复方案:
不要依赖前端数据来做身份管理,用户提交的信息一个标点符号也别信!
这里的修复就是将isadmin参数转移到数据库中,身份认证通过查询该用户在数据库中的isadmin字段值是否为1来进行身份校验,而不是依赖cookie中的参数识别。

三、条件竞争

朋友的系统中调用了个fofa的接口,采用了多进程并发,然而多进程并发做数据库写入还好,如果是写文件将会造成条件竞争的问题,我们来简单看下核心代码:

def getinfo(p,q):
response=requests.get("https://fofa.so/result?full=true&page="+ str(p) +"&qbase64="+str(q),headers=headers)
r1= re.compile(r'.*?)
r = r1.findall(response.text)return rdef saveinfo(p,q,d):print('当前进度:第{}页'.format(p))
res=getinfo(p,q)print(res)for i in res:
fw=open('ip.txt','a')
fw.write(i+'\n')
fw.close()
d.put(p)def poolmana(pages,keyword):
p = Pool(10)
q = Manager().Queue()for i in range(pages//5):for j in range(i*5,i*5+5):
p.apply_async(saveinfo, args=(j+1, keyword,q))
sleep(30)
p.close()
p.join()print('读取完成>>>>>\n请查看当前路径下文件:ip.txt')

瞅瞅这破格式,当频繁的做文件打开、写入、关闭操作,会导致进程之间相互影响,一个进程还没写入完毕,另一个进程就强行结束了文件流,最终造成了如下破格式:

0d4602c938b234c155a7f74cd9df4bcb.png

这种情况下如何修复呢?相比大家第一个想到的是进程锁,说白了就是一个进程写入时,其它进程暂时等待,这势必会影响执行效率,所以再次推荐另一个方案,异步并发,使用callback来完成,修复如下。

def getinfo(p,q,d):
print('当前进度:第{}页'.format(p))
response=requests.get("https://fofa.so/result?full=true&page="+ str(p) +"&qbase64="+str(q),headers=headers)
r1= re.compile(r'.*?)
r = r1.findall(response.text)
d.put(p)return rdef saveinfo(result):print(result)for i in result:
fw=open('ip.txt','a')
fw.write(i+'\n')
fw.close()def poolmana(pages,keyword):
p = Pool(10)
q = Manager().Queue()for i in range(pages//5):for j in range(i*5,i*5+5):
p.apply_async(getinfo, args=(j+1, keyword,q),callback=saveinfo)
sleep(30)
p.close()
p.join()print('读取完成>>>>>\n请查看当前路径下文件:ip.txt')
异步并发

在这里我们用到的技术就是异步并发,多进程并发做网络请求,文件写入由callback异步完成,防止条件竞争漏洞的形成,也通过这个小实例告诉大家,不要无脑lock,那样会失去并发的意义。

END

最后祝大家国庆快乐,有时间还请多陪陪父母,也许工作了你才会明白,跟父母相处的时间是多么少。真的不需要你做什么,待在他们视野里,聊点有的没的什么都好,只要你在,共勉。

5a168ffcf58c929aecae906bbcf1a1f9.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值