1.After downloading the source code, go to the add
function in the app/adminapi/controller/v1/marketing/live/LiveGoods.php
file.
`public function add()
{
[$goods_info] = $this->request->postMore([
['goods_info', []]
], true);
if (!$goods_info) return app('json')->fail('请选择商品');
foreach ($goods_info as $goods) {
if (!$goods['id']) return app('json')->fail('请选择商品');
if (!$goods['store_name']) return app('json')->fail('请输入名称');
if (!$goods['image']) return app('json')->fail('请选择背景图');
if (!$goods['price']) return app('json')->fail('请输入直播价格');
if ($goods['price'] <= 0) return app('json')->fail('直播价格必须大于0');
}
$this->services->add($goods_info);
return app('json')->success('添加成功');
}`
2.The function accepts a goods_info
parameter from the front end and assigns it to the variable $goods_info
[$goods_info] = $this->request->postMore([ ['goods_info', []] ], true);
3.Enter the add
function of the $services
object by tracking the $goods_info
parameter
$this->services->add($goods_info);
4.In this class you can see services
declared as class LiveGoodsServices
public function __construct(App $app, LiveGoodsServices $services) { parent::__construct($app); $this->services = $services; }
5.In this class, you can see that services
is declared as class LiveGoodsS
to the file app/services/activity/live/LiveGoodsServices.php
, and the source code of the function add
is as follows: services
public function add(array $goods_info)
{
$product_ids = array_column($goods_info, 'id');
$this->create($product_ids);
$miniUpload = MiniProgramService::materialTemporaryService();
/** @var DownloadImageService $download */
$download = app()->make(DownloadImageService::class);
$dataAll = $data = [];
$time = time();
foreach ($goods_info as $product) {
$data = [
'product_id' => $product['id'],
'name' => Str::substrUTf8($product['store_name'], 12, 'UTF-8', ''),
'cover_img' => $product['image'] ?? '',
'price_type' => 1,
'cost_price' => $product['cost_price'] ?? 0.00,
'price' => $product['price'] ?? 0.00,
'url' => 'pages/goods_details/index?id=' . $product['id'],
'sort' => $product['sort'] ?? 0,
'add_time' => $time
];
try {
$path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path'];
$coverImgUrl = $miniUpload->uploadImage($path)->media_id;
@Unlink($path);
} catch (\Throwable $e) {
Log::error('添加直播商品图片错误,原因:' . $e->getMessage());
$coverImgUrl = $data['cover_img'];
}
$res = MiniProgramService::addGoods($coverImgUrl, $data['name'], $data['price_type'], $data['url'], floatval($data['price']));
$data['goods_id'] = $res['goodsId'];
$data['audit_id'] = $res['auditId'];
$data['audit_status'] = 1;
$dataAll[] = $data;
}
if (!$goods = $this->dao->saveAll($dataAll)) {
throw new AdminException('添加商品失败');
}
return true;
}`
6.Continue to track the $goods_info
variable, the function assigns the information in the $goods_info
array to $data
foreach ($goods_info as $product) { $data = [ 'product_id' => $product['id'], 'name' => Str::substrUTf8($product['store_name'], 12, 'UTF-8', ''), 'cover_img' => $product['image'] ?? '', 'price_type' => 1, 'cost_price' => $product['cost_price'] ?? 0.00, 'price' => $product['price'] ?? 0.00, 'url' => 'pages/goods_details/index?id=' . $product['id'], 'sort' => $product['sort'] ?? 0, 'add_time' => $time ];
7.Continue reading down, pass the cover_img
value of the $data
array to the downloadImage
function, and follow up
$path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path'];
8.Go to the file crmeb/services/DownloadImageService.php
, the source code of the function downloadImage
is as follows:
public function downloadImage(string $url, $name = '') { if (!$name) { //TODO 获取要下载的文件名称 $downloadImageInfo = $this->getImageExtname($url); $name = $downloadImageInfo['file_name']; if (!$name) throw new ValidateException('上传图片不存在'); } if (strstr($url, 'http://') === false && strstr($url, 'https://') === false) { $url = 'http:' . $url; } $url = str_replace('https://', 'http://', $url); if ($this->path == 'attach') { $date_dir = date('Y') . DIRECTORY_SEPARATOR . date('m') . DIRECTORY_SEPARATOR . date('d'); $to_path = $this->path . '/' . $date_dir; } else { $to_path = $this->path; } $upload = UploadService::init(1); if (!file_exists($upload->uploadDir($to_path) . '/' . $name)) { ob_start(); readfile($url); $content = ob_get_contents(); ob_end_clean(); $size = strlen(trim($content)); if (!$content || $size <= 2) throw new ValidateException('图片流获取失败'); if ($upload->to($to_path)->down($content, $name) === false) { throw new ValidateException('图片下载失败'); } $imageInfo = $upload->getDownloadInfo(); $path = $imageInfo['dir']; if ($this->thumb) { Image::open(root_path() . 'public' . $path)->thumb($this->thumbWidth, $this->thumHeight)->save(root_path() . 'public' . $path); $this->thumb = false; } } else { $path = '/uploads/' . $to_path . '/' . $name; $imageInfo['name'] = $name; } $date['path'] = $path; $date['name'] = $imageInfo['name']; $date['size'] = $imageInfo['size'] ?? ''; $date['mime'] = $imageInfo['type'] ?? ''; $date['image_type'] = 1; $date['is_exists'] = false; return $date; }
9.The controllable variable $data['cover_img']
is passed as a parameter to $url
, continue to track $url
, the function obtains the file content from the address specified by $rul
, and saves it in the variable $content
ob_start(); readfile($url); $content = ob_get_contents(); ob_end_clean();
10.Track $content
, pass $content
to function $down
$upload->to($to_path)->down($content, $name)
11.Go to the file crmeb/services/upload/storage/Local.php
, the source code of the function down
is as follows:
public function down(string $fileContent, string $key = null) { if (!$key) { $key = $this->saveFileName(); } $dir = $this->uploadDir($this->path); if (!$this->validDir($dir)) { return $this->setError('Failed to generate upload directory, please check the permission!'); } $fileName = $dir . '/' . $key; file_put_contents($fileName, $fileContent); $this->downFileInfo->downloadInfo = new File($fileName); $this->downFileInfo->downloadRealName = $key; $this->downFileInfo->downloadFileName = $key; $this->downFileInfo->downloadFilePath = $this->defaultPath . '/' . $this->path . '/' . $key; return $this->downFileInfo; }
12.Continue to track $fileContent
, the function writes the contents of $fileContent
to the file $fileName
file_put_contents($fileName, $fileContent);
13.Now let's take a look at the value of $fileNmae
, go back to the function downloadImage
of the file crmeb/services/DownloadImageService.php
, $url
is a value we can control, passed to the getImageExtname
function of this class
$downloadImageInfo = $this->getImageExtname($url);
14.The source code of getImageExtname
is as follows, which probably means that the $url
link is encrypted by md5 and then copied to file_name
as the new name of the file and then returned to the downloadImage
function:
public function getImageExtname($url = '', $ex = 'jpg') { $_empty = ['file_name' => '', 'ext_name' => $ex]; if (!$url) return $_empty; if (strpos($url, '?')) { $_tarr = explode('?', $url); $url = trim($_tarr[0]); } $arr = explode('.', $url); if (!is_array($arr) || count($arr) <= 1) return $_empty; $ext_name = trim($arr[count($arr) - 1]); $ext_name = !$ext_name ? $ex : $ext_name; return ['file_name' => md5($url) . '.' . $ext_name, 'ext_name' => $ext_name]; }
15.The downloadImage
function assigns the returned value of file_name
to the variable $name
0
$name = $downloadImageInfo['file_name'];
16.Go back to the down
function, splicing the incoming $name
as the parameter $key
value to the variable $dir
as the location of the file, so that we can control the content of the function file_put_contents
and know the file s position
$fileName = $dir . '/' . $key;
17.But there is a problem, go back to the function add
of the file app/services/activity/live/LiveGoodsServices.php
and find that the last file we stored will be deleted using @unlink($path)
, here you can pass appid
without WeChat configuration throws an exception when executing $miniUpload->uploadImage($path)->media_id;
to skip the execution of @unlink($path)
and execute the code in the catch
try { $path = root_path() . 'public' . $download->thumb(true)->downloadImage($data['cover_img'])['path']; $coverImgUrl = $miniUpload->uploadImage($path)->media_id; @unlink($path); } catch (\Throwable $e) { Log::error('添加直播商品图片错误,原因:' . $e->getMessage()); $coverImgUrl = $data['cover_img']; }
18.After setting up the environment locally, log in to the background,Put malicious code on the server and start the file download service
![image-20221025030612377](https://user-images.githubusercontent.com/50450328/197783399-9c809d35-2965-455e-a61f-65a116537b07.png)
19.Enter the background, if the following page has set appid, set it to empty
![image-20221025034814652](https://user-images.githubusercontent.com/50450328/197783553-38d501af-c6e7-402e-a5a9-30f7c9938e64.png)
20.Enter the live broadcast product management interface in the background
![image-20221025025001348](https://user-images.githubusercontent.com/50450328/197783692-800c7201-a4ef-4325-a406-ec797ac0d196.png)
21.Click to add a product, select the product and submit the packet capture, change the image parameter to the malicious file address on our server
![image-20221025030807714](https://user-images.githubusercontent.com/50450328/197783850-bffb9bce-3915-49c6-a7a7-2e8f54003931.png)
22.Then md5 encode the server file address
![image-20221025030938698](https://user-images.githubusercontent.com/50450328/197784035-e41b88c8-885e-4654-b70f-082bb4b3d666.png)
23.The access path is as follows:http://domain.com/uploads/attach/{year}/{month}/{day}/{md5 encoding of remote file url}.php
Code executed successfully:
![image-20221025031209417](https://user-images.githubusercontent.com/50450328/197784281-abae59c4-bf0e-4832-b906-6bab2f67721d.png)