You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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: servicespublic 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 $name0 $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
1.After downloading the source code, go to the
add
function in theapp/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 classLiveGoodsServices
public function __construct(App $app, LiveGoodsServices $services) { parent::__construct($app); $this->services = $services; }
5.In this class, you can see that
public function add(array $goods_info)
services
is declared as classLiveGoodsS
to the fileapp/services/activity/live/LiveGoodsServices.php
, and the source code of the functionadd
is as follows: services{
$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 thedownloadImage
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 functiondownloadImage
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 functiondown
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 functiondownloadImage
of the filecrmeb/services/DownloadImageService.php
,$url
is a value we can control, passed to thegetImageExtname
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 tofile_name
as the new name of the file and then returned to thedownloadImage
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 offile_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 functionfile_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 fileapp/services/activity/live/LiveGoodsServices.php
and find that the last file we stored will be deleted using@unlink($path)
, here you can passappid
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 catchtry { $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:
The text was updated successfully, but these errors were encountered: