0x03 FILTER_VALIDATE_EMAIL 绕过
这就是今天第一个trick。这个点早在当初PHPMailer的CVE-2016-10033就提到过。
RFC 3696规定,邮箱地址分为local part和domain part两部分。local part中包含特殊字符,需要如下处理:
- 将特殊字符用
\
转义,如Joe\'Blow@example.com
- 或将local part包裹在双引号中,如
"Joe'Blow"@example.com
- local part长度不超过64个字符
虽然PHP没有完全按照RFC 3696进行检测,但支持上述第2种写法。所以,我们可以利用之绕过FILTER_VALIDATE_EMAIL
的检测。
因为代码中邮箱是用户名、@、Host三者拼接而成,但用户名是经过了转义的,所以单引号只能放在Host中。我们可以传入用户名为"
,Host为aaa'"@example.com
,最后拼接出来的邮箱为"@aaa'"@example.com
。
这个邮箱是合法的:
这个邮箱包含单引号,将闭合SQL语句中原本的单引号,造成SQL注入漏洞。
0x04 绕过 Nginx Host 限制
这是今天第二个trick。
我们尝试向目标注册页面发送刚才构造好的用户名和Host:
直接显示404,似乎并没有进入PHP的处理过程。
这就回到问题的本质了,Host头究竟是做什么的?
众所周知,如果我们在浏览器里输入http://2018.mhz.pw
,浏览器将先请求DNS服务器,获取到目标服务器的IP地址,之后的TCP通信将和域名没有关系。那么,如果一个服务器上有多个网站,那么Nginx在接收到HTTP包后,将如何区分?
这就是Host的作用:用来区分用户访问的究竟是哪个网站(在Nginx中就是Server块)。
如果Nginx发现我们传入的Host找不到对应的Server块,将会发送给默认的Server块,也就是我们通过IP地址直接访问的那个Nginx默认页面:
默认网站并没有/main/register
这个请求的处理方法,所以自然会返回404。
这里给出解决这个问题的两个方法,也许还有更多新方法我没有想到,欢迎补充。
法1
Nginx在处理Host的时候,会将Host用冒号分割成hostname和port,port部分被丢弃。所以,我们可以设置Host的值为2018.mhz.pw:xxx'"@example.com
,这样就能访问到目标Server块
如上图,成功触发SQL报错。
法2
当我们传入两个Host头的时候,Nginx将以第一个为准,而PHP-FPM将以第二个为准。
也就是说,如果我传入:
Host: 2018.mhz.pw
Host: xxx'"@example.com
Nginx将认为Host为2018.mhz.pw
,并交给目标Server块处理;但PHP中使用$_SERVER['HTTP_HOST']
取到的值却是xxx'"@example.com
。这样也可以绕过:
这个方法我以前在某群里提到过,只有Nginx+PHP会出现这个问题,Apache的情况下将会是另一个样子,此处不展开讨论。