知识点:
CVE-2021-41773是Apache HTTP Server 2.4.49版本的目录路劲穿越漏洞。
原理如下:
简单来说: 检测路径中是否存在%字符,并且如果%后面的两个字符是十六进制字符,就会对后面的两个字符进行url解码转化,如路径中有%2e./,则会解码为../,转换后判断是否存在../,加入路径中使用.%2e/则能绕过,导致目录穿越漏洞,因为当遍历到第一个.时,后面两个字符是%2并不是./,就不会被处理,绕过检测。
arrary_merge()函数:array_merge() 函数用于把一个或多个数组合并为一个数组,如果两个或更多个数组元素有相同的键名,则最后的元素会覆盖其他元素。
extract():extract(array,extract_rules,prefix)
extract() 函数从数组中将变量导入到当前的符号表。
该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量,该函数返回成功设置的变量数目。
如:
<?php
$code='';
$arg='';
$flag=array('code'=>'a','arg'=>'b');
extract($flag);
print(extract($flag));
printf($code);
print($arg)
?>
运行结果:2ab
<?php
$a1=array("a"=>"a","b"=>"b");
$a2=array("c"=>"c","b"=>"d");
print_r(array_merge($a1,$a2));
?>
输出:
Array
(
[a] => a
[b] => d
[c] => c
)
题目详解:
1,进入题目:
很明显需要使用路径漏洞进行穿越,其实使用dirsearch扫一遍也能扫出来。
利用BP进行抓包修改:
读取到源代码:
<h1>Welcome To GFCTF 12th!!</h1>
<?php
error_reporting(0);
define("main","main");
include "Class.php";
$temp = new Temp($_POST);
$temp->display($_GET['filename']);
?>
可以使用GET传入参数filename并且类Temp接收POST数据传送过来的值。
可以看到还有一个Class.php,继续进行源代码读取:
源代码:
<?php
defined('main') or die("no!!");
Class Temp{
private $date=['version'=>'1.0','img'=>'https://www.apache.org/img/asf-estd-1999-logo.jpg'];
private $template;
public function __construct($data){
$this->date = array_merge($this->date,$data);
}
public function getTempName($template,$dir){
if($dir === 'admin'){
$this->template = str_replace('..','','./template/admin/'.$template);
if(!is_file($this->template)){
die("no!!");
}
}
else{
$this->template = './template/index.html';
}
}
public function display($template,$space=''){
extract($this->date);
$this->getTempName($template,$space);
include($this->template);
}
public function listdata($_params){
$system = [
'db' => '',
'app' => '',
'num' => '',
'sum' => '',
'form' => '',
'page' => '',
'site' => '',
'flag' => '',
'not_flag' => '',
'show_flag' => '',
'more' => '',
'catid' => '',
'field' => '',
'order' => '',
'space' => '',
'table' => '',
'table_site' => '',
'total' => '',
'join' => '',
'on' => '',
'action' => '',
'return' => '',
'sbpage' => '',
'module' => '',
'urlrule' => '',
'pagesize' => '',
'pagefile' => '',
];
$param = $where = [];
$_params = trim($_params);
$params = explode(' ', $_params);
if (in_array($params[0], ['list','function'])) {
$params[0] = 'action='.$params[0];
}
foreach ($params as $t) {
$var = substr($t, 0, strpos($t, '='));
$val = substr($t, strpos($t, '=') + 1);
if (!$var) {
continue;
}
if (isset($system[$var])) {
$system[$var] = $val;
} else {
$param[$var] = $val;
}
}
// action
switch ($system['action']) {
case 'function':
if (!isset($param['name'])) {
return 'hacker!!';
} elseif (!function_exists($param['name'])) {
return 'hacker!!';
}
$force = $param['force'];
if (!$force) {
$p = [];
foreach ($param as $var => $t) {
if (strpos($var, 'param') === 0) {
$n = intval(substr($var, 5));
$p[$n] = $t;
}
}
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']);
}
return $rt;
}else{
return null;
}
case 'list':
return json_encode($this->date);
}
return null;
}
}
访问代码中/template/admin路径:
对源代码进行分析:
public function __construct($data){
$this->date = array_merge($this->date,$data);
}
public function getTempName($template,$dir){
if($dir === 'admin'){
$this->template = str_replace('..','','./template/admin/'.$template);
if(!is_file($this->template)){
die("no!!");
}
}
else{
$this->template = './template/index.html';
}
}
public function display($template,$space=''){
extract($this->date);
$this->getTempName($template,$space);
include($this->template);
}
对display传进来的的值进行extract后进入getTempName(),然后文件包含$this->template,如果$dir==admin,拼接/template/admin/.$template并且进行包含。
继续分析下面源代码:
数组定义的流程:
$params = explode(' ', $_params); #利用空格分解$_params变量为数组
if (in_array($params[0], ['list','function'])) { #$params[0]是为在list或function
$params[0] = 'action='.$params[0];
}
foreach ($params as $t) {
$var = substr($t, 0, strpos($t, '=')); #取$t的等号前面的值
$val = substr($t, strpos($t, '=') + 1); #取$t的等号后面的值
if (!$var) {
continue;
}
if (isset($system[$var])) {
$system[$var] = $val;
} else {
$param[$var] = $val;
}
}
// action
switch ($system['action']) { #对action进行分情况处理
case 'function':
if (!isset($param['name'])) { #一定要存在name变量的key
return 'hacker!!';
} elseif (!function_exists($param['name'])) { #并且name必须被定义
return 'hacker!!';
}
$force = $param['force'];
if (!$force) {
$p = [];
foreach ($param as $var => $t) {
if (strpos($var, 'param') === 0) {
$n = intval(substr($var, 5));
$p[$n] = $t;
}
}
if ($p) {
$rt = call_user_func_array($param['name'], $p);
} else {
$rt = call_user_func($param['name']); #回调函数
}
return $rt;
}else{
return null;
}
case 'list':
return json_encode($this->date);
}
很明显需要利用call_user_func()进行函数的回调,那么就调用listdata的数组定义,要调用listdata就得把/template/admin/index.html包含进来,为mod传值,action的值为function,可以传入name=phpinfo。这样就会调用listdata函数,并且由于system中不存在system[name]$,所以param[name]=phpinfo,执行了phpinfo()函数。
payload为: