phpList 3.5.0弱类型漏洞复现及渗透测试实战经验分享
在一次渗透测试过程中,甲方的维护工作做得比较到位。所以,并没有发现什么漏洞。而在即将结束的时候,我依然没有取得大的突破。

我进行了一番仔细的信息搜集。接着发现系统里有 phpList。这让我眼前一亮,因为我记得之前看到过 phplist 的相关漏洞。
我随即开始查找 phplist 的历史漏洞。经过一番操作后,最终明确 phplist 的版本是 3.5.0,其存在的漏洞是弱类型漏洞。利用这个弱类型漏洞成功登录进去,之后事情就进展得很顺利。
因为是在客户机器上进行的渗透,所以没有截图之类的东西……接下来我在本机上搭建了一个环境,要对 phplist3.5.0 的弱类型漏洞进行复现。
复现过程
phpList 3.5.0 版本存在安全漏洞,其编号为 CVE-2020-5847。此漏洞是因为程序在处理开头为 0e 且之后全部为数字字符的哈希值时出现错误。远程攻击者能够借助该漏洞绕过管理员账户的身份验证。
先下载 phpList 3.5.0-RC1,接着进行解压操作,之后找到该目录。
将这个目录拷贝到网站目录下,并重命名。
对 config/config.php 配置文件进行修改,明确需连接的数据库以及账户密码的相关设置。
同时建立phplistdb数据库。
访问;
接下来初始化安装,设置管理员的账号密码:
这里的密码需要设置为经过 sha256 处理后开头为 0e 的字符串,例如 TyNOQHUS。
我们再次访问后台。我们以密码 34250003024812 进行登录。该密码 sha256 后是以 0e 开头的。
“TyNOQHUS——hash :
该字符串为“0e66298694359207596086558843543959518835691168370379069085300385”。
“34250003024812——hash:
“0e46289032038065916139621039085883773413820991920706299695051332” 这个字符串具有特定的编码或标识意义,可能在特定的系统、数据或情境中被使用和识别。它由一系列数字组成,每个数字都有其特定的位置和价值。这些数字的组合形成了一个独特的代码,用于区分或标识特定的对象、事件或信息。
登录成功,漏洞验证成功:
下面还是具体分析一下漏洞处的代码。
验证管理员登录的PHP文件为:
“phpListAdminAuthentication.php”这个文件。
登录时,会传入密码。经过 if 判断后,能够看到$encryptedPass(密码 sha256 后的的值)是通过==来判断其与数据库中的值是否相同,也就是$encryptedPass == $passwordDB。
PHP 存在弱类型比较的情况,这会导致 0exxxxx 与 0eyyyyy 相等(因为会将每一个以“0e”开头的哈希值都解读为 0),其具体代码呈现如下:
什么是PHP弱类型
首先先说一下,什么是强类型和弱类型?
语言通常分为强类型和弱类型这两种。强类型指的是强制规定数据类型的语言。也就是说,一个变量一旦被定义为某个类型,在不进行强制类型转换的情况下,这个变量就始终是这个类型。
在使用变量之前,需要声明变量的类型和名称。并且,在未经强制转换的情况下,不允许两种不同类型的变量进行相互操作。然而,弱类型则能够随意地转换变量的类型。
PHP是最受欢迎的开源脚本语言,在 Web 开发领域的应用越来越多。并且 PHP 属于弱类型语言,在定义变量时无需声明其类型。
弱类型给程序员书写代码带来了很大便利。然而在安全领域,特性意味着漏洞。这些特性在代码中常常是漏洞最容易出现的地方。
PHP 官方给出了类型比较表。该表格显示了 PHP 类型以及比较运算符在松散比较和严格比较时所起的作用。
松散比较:
严格比较:
总结一下PHP弱类型产生漏洞的原理:
通过以上方式能够产生诸如密码重置这样的情况,还能绕过管理员账户的身份验证,以及出现注入、cookie 伪造等弱类型漏洞。并且一些 CTF 试题会借助这些弱类型来设置诸如 MD5 碰撞、十六进制转换等问题。
下面通过一些漏洞实例来进行说明。
CVE-2014-0166 是关于 WordPress 的 Cookie 伪造漏洞。
在wordpress-3.8.2的补丁中有以下代码:
在 wordpress - 3.8.1 中,其对应的 PHP 代码是如下这样的:
通过对比我们自然把全部的关注点放到!=与!==上来。
再看php manual中给出的例子:
var_dump(0 等于 "a"); // 0 等于 0 -> true ,这里比较的是值的相等性,0 与字符串 "a" 不相等,所以结果为 false ,而不是 true 。var_dump("1" == "1"); // 1 与 1 相等,结果为 truevar_dump("10" 等于 "1e1"); // 10 等于 10 -> truevar_dump(100 等于 "1e2"); // 100 等于 100 -> true?>
字符串在与数字比较前会自动转换为数字,所以0=="a"了。
回到wordpress的验证代码上来。先生成依据用户名($username)、密码($pass_frag)、cookie 有效期 ($expiration)以及 wp-config.php 中的 key($key)这四个信息,算出与之对应的$hash。接着,用从 cookie 中获取到的$hmac 值和算出的$hash 进行比较($hmac 不等于$hash),以此来验证 cookie 的有效性。
cookie的格式是这样的:
wordpress 的哈希值由 url、用户名、过期时间和 HMAC 组成,其形式为 username|expiration|hmac
我们可以控制的变量有两个,分别是$username 和$expiration。$username 是需要固定的。这样一来,我们就能够通过对 cookie 中的$expiration 进行控制,从而改变$hash 的值。之后,再把 cookie 中的$hmac 设置为 0。
只要持续改变$expiration,然后去寻找能够满足$hash=="0"的$hash,这样就成功地伪造了有效的 cookie。
HDwiki sql注入
在/control/list.php中,代码如下:
分析代码后可以得知,从 GET 中获取到了 $doctype,也就是 $doctype = $this->get[2],随后进入了一个 switch 语句。接着我们发现,后面直接把 $doctype 运用到了 SQL 语句中。
使用“focus”表,通过“type=$doctype”条件进行查询,获取到的总数赋值给变量$count,即$count 等于$this->db->fetch_total('focus',"type=$doctype")。
只要这个 switch 语句不会对 $doctype 的值产生影响,那么后面就可以进行注入了。
如果进入 default ,会将 $doctype 改为 1 。
这里犯了一个“弱类型”的错误。当出现一个字符串与一个数字进行比较的情况时,会先把字符串进行强制类型转换,然后再与数字进行比较。
所以当我传入 doctype 为 2xxxx 时,实际上会进入 case 2 而非 default。在跳出 switch 语句后,doctype 的值依然是 2xxxx,并非 2。
所以之后的:
$count=$this->db->fetch_total('focus',"type=$doctype");
$doctype带入SQL语句造成注入。
CTF中的利用
《MD5碰撞》:
如果存在 $_GET['Username'] 并且存在 $_GET['password'] 。$logined = true;获取的 GET 请求中的 Username 被赋值给了 $Username 。获取到的名为 password 的变量的值为通过 GET 方式传递过来的 password 的值。即通过 GET 请求获取到的 password 被赋值给了变量 $password 。如果 $Username 不是字母字符,那么 $logined 为 false。假如密码不是数字性的,那么登录状态就为 false。如果 md5 处理后的 $Username 不等于 md5 处理后的 $password,那么 $logined 为 false。if ($logined){echo "successful";}else{echo "login failed!";}}?>
这个题目的意思是,输入一个数字,同时输入一个字符串,只有当这两者的 MD5 值相同时,才能够得到 successful。
在进行比较时,会将其视为科学计数法。因此,无论 0e 后面是什么,0 的多少次方都为 0。
我们只需输入一个数字和字符串,对其进行 MD5 加密后都得到 0e,就能得出答案。
md5 的值为 '240610708' 与 md5 的值为 'QNKCDZO' 是相等的。
成功绕过。
《起名字真难》:
如果函数 noother_says_correct 接收到一个参数 $number,那么它会进行相应的操作或判断,具体的行为取决于该函数内部的逻辑和设计。这个函数可能会根据 $number 的值来执行不同的代码路径,以实现特定的功能或返回特定的结果。{$one = ord('1');$nine = ord('9');for ($i = 0; $i < strlen($number); $i++){$digit = ord($number{$i});如果(数字大于等于 1)并且(数字小于等于 9),那么(数字是一位数);如果(数字大于等于 10)并且(数字小于等于 99),那么(数字是两位数);如果(数字大于等于 100)并且(数字小于等于 999),那么(数字是三位数);如果(数字大于等于 1000)并且(数字小于等于 9999),那么(数字是四位数);如果(数字大于等于 10000)并且(数字小于等于 99999),那么(数字是五位数);如果(数字大于等于 100000)并且(数字小于等于 999999),那么(数字是六位数);如果(数字大于等于 1000000)并且(数字小于等于 9999999),那么(数字是七位数);如果(数字大于等于 10000000)并且(数字小于等于 99999999),那么(数字是八位数);如果(数字大于等于 100000000)并且(数字小于等于 999999999),那么(数字是九位数);如果(数字大于等于 1000000000),那么(数字是十位数及以上)。<= $nine) ){return false;}}如果数字等于 '54975581388',则返回 true;否则返回 false。}$flag='*******';如果没有其他的(判断)认为 $_GET['key'] 是正确的。echo $flag;elseecho 'access denied';?>
题目大致的意思为,需输入一串 key,此 key 不能是数字形式,然而却必须与数字 54975581388 相等,这样才能拿到 flag。
看完题目就知道要求字符串和数字进行比较,所以:
$number == '54975581388';
看到这便联想到了弱类型,与之相匹配的十六进制字符串是 0xccccccccc,而这个字符串对应的数字是 54975581388。
全不是数字,自然就绕过了,得到flag。
结语
看到这里,大家应该都对 PHP 弱类型比较有所了解了。同时,还有许多其他的漏洞实例存在,不过在此就不一一进行列举了。
下面列举一些以0e开头的字符串的md5的hash值:
s214587387a:
0e848240448830537924465865611904 这串字符具有特定的标识意义,它可能代表着某个特定的信息、数据或代码片段等,在特定的情境或系统中有着特定的用途和价值。
s1502113478a:
0e861580163291561247404381396064 这个内容;它具有特定的标识或特征;可能在特定的情境或系统中有其特定的意义或用途。
s1091221200a:
0e940624217856561557816327384675 这串字符本身就具有特定的含义和价值,可能在特定的系统或情境中有着重要的作用。它或许是某个数据的标识,也可能是一段代码的关键部分,或者是一个特定操作的触发因素。无论其具体用途如何,这串字符都承载着一定的信息和意义。
s1665632922a:
0e731198061491163073197128363787 这个内容没有具体的上下文信息,仅从这串字符本身来看,它是一个特定的编码或标识,可能在某个特定的系统、场景或数据集中具有特定的含义。但仅凭借这串字符,无法确切知晓其具体用途和所代表的信息。
s1885207154a:
0e509367213418206700842008763514 是一个特定的编码或标识,它可能在特定的系统或情境中有特定的含义和用途。这个编码可能与数据、文件、事件或其他相关的事物相关联。它可能是用于识别、跟踪或管理特定对象或信息的一种方式。具体的含义和用途需要根据相关的背景和上下文来确定。
s1836677006a:
关于这串字符的相关信息等。
s1665632922a:
0e731198061491163073197128363787;
s878926199a:
0e545993274517709034328855841020 这个内容;它是一个特定的标识或编码等;有着独特的存在形式和意义;在特定的情境或系统中具有特定的作用。
QLTHNDT:
0e405967825401955372549139051580 这串字符具有特定的标识意义,它在特定的情境或系统中有着独特的作用和价值。
QNKCDZO:
0e830400451993494058024219903391 这串字符具有特定的标识意义,可能在特定的系统或情境中被使用,它有着独特的编码和组合形式,以区分其他不同的信息或对象。
EEIZDOI:
0e782601363539291779881938479162 这个内容没有具体的上下文信息,仅从这串字符本身来看,它是一个特定的标识或数据序列,没有更多的含义或解释。它可能在某个特定的系统、程序或情境中具有特定的作用,但仅从这几个字符本身无法确切知晓其具体用途。
TUFEPMC:
0e839407194569345277863905212547 这串字符具有特定的标识意义;它是一个独一无二的编码组合;在特定的系统或情境中被用于区分和标识特定的对象或信息;具有明确的唯一性和特定性。
UTIPEZQ:
0e382098788231234954670291303879 这串字符具有特定的标识意义。它是一个独一无二的编码或标识,用于在特定的系统或情境中进行区分和识别。可能与某个特定的项目、数据或操作相关联,在相关的领域或环境中具有重要的作用。
UYXFLOI:
0e552539585246568817348686838809 这串字符具有特定的标识意义,它可能代表着某个特定的信息、数据或代码等,在特定的系统或情境中有着独特的作用和价值。
IHKFRNS:
0e256160682445802696926137988570 这个内容本身没有具体含义的上下文,仅从这串字符来看,它就是这样一个特定的组合。
240610708:
0e462097431906509019562988736854 这串字符具有特定的标识意义。它可能是某个系统或数据中的唯一编码,用于区分不同的对象或记录。在特定的情境中,它承载着重要的信息和价值。
314282422:
此句仅为一串字符,没有具体的句子内容可供改写呀,你可以给我提供具体的句子呢。
571579406:
0e972379832854295224118025748221 是一个特定的标识或代码,可能在特定的系统或情境中有其特定的含义和用途。它可能与某个数据、事件、操作或对象相关联,具体的意义需要根据相关的背景和上下文来确定。
903251147:
0e174510503823932942361353209384 这个内容没有具体的上下文信息,仅从这串字符本身来看,它只是一个特定的标识或数据序列,可能在某个特定的系统、环境或情境中有其特定的意义和用途。
1110242161:
0e435874558488625891324861198103 这个内容没有具体的上下文,难以准确理解其含义和进行改写。如果能提供更多关于这个内容的背景信息或具体要求,我将能更好地为你提供帮助。
1320830526:
0e912095958985483346995414060832 这个内容。
1586264293:
0e622743671155995737639662718498 这串字符;这串特定的数字组合;具有该特定编码的内容;以 0e622743671155995737639662718498 为标识的事物;包含 0e622743671155995737639662718498 的信息。
感谢阅读,下次再见!
侵权请私聊公众号删文