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
19.Enter the background, if the following page has set appid, set it to empty
20.Enter the live broadcast product management interface in the background
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
22.Then md5 encode the server file address
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: