一次代码审计项目案例

admin 2024年10月31日09:50:06评论14 views字数 4129阅读13分45秒阅读模式

免责申明

本文章仅用于信息安全防御技术分享,因用于其他用途而产生不良后果,作者不承担任何法律责任,请严格遵循中华人民共和国相关法律法规,禁止做一切违法犯罪行为。

一、前言

好久没有给大家更新了,这次记录一下相关的一个代码审计案例项目,这个项目是自己上周做的了,今天来大概复盘一下。

二、审计经过

    当时客户说只要前台漏洞,并且基本上规定了只要前台RCE以及相关的前台注入等等,危害性较大的漏洞才可以交差,这里也是运气好水了一个。

废话不多说了,这里来看案例:

首先打开源码其实可以看到是一套基于ThinkPHP的代码:

一次代码审计项目案例

大概点开lou一眼还是很有TP的味道的,然后自己进行查找,当时先看的登录点分析。

下面这个代码是案例相关代码,不想看直接跳过就行,下面定位有问题的点。

    public function login(){        if (empty($this->imageKey) || empty(Cache::get($this->imageKey))) {            return json(["status" => 1, "msg" => "缺少图片验证码信息"]);        }        $imageData = Cache::get($this->imageKey);        if ($imageData['past'] === 0) {            return json(["status" => 1, "msg" => "图片验证码未通过"]);        }        $request = $this->request;        $username = $request->param('username', '');        $ret = check_pwd_wrong_times($username);        if ($ret['err']) {            return json(["status" => 1, "msg" => $ret['msg'], 'state' => 0]);        }        $password = $request->param('password', '');        if (!$username) {            return json(["status" => 1, "msg" => "帐号不能为空"]);        }        if (!$password) {            return json(["status" => 1, "msg" => "密码不能为空"]);        }        $ip = $this->request->ip();        $cacheKey = 'login_attempts_num' . $ip . $password;        $lastAttemptTimeKey = 'login_attempts_' . $ip . $password;        // 获取已登录尝试次数        $attempts = Cache::get($cacheKey, 0);        // 设置限制的尝试次数        $maxAttempts = 3;        // 设置登录失败后的等待时间(单位:秒)        $waitTime = 60; // 1分钟        if ($attempts >= $maxAttempts) {            // 获取上一次登录尝试的时间            $lastAttemptTime = Cache::get($lastAttemptTimeKey, 0);            $currentTime = time();            $remainingTime = $lastAttemptTime + $waitTime - $currentTime;            if ($remainingTime > 0) {                return json(["status" => 1, "msg" => '登录失败次数过多,请稍后再试']);            }        }        $member = Member::where("mobile", $username)->find();        $memberlogs = new Memberlogs();        if (!$member) {            Cache::inc($cacheKey, 1);            Cache::set($lastAttemptTimeKey, time());            /**登录日志**/            $data['userid'] = 0;            $data['username'] = $username;            $data['memo'] = "尝试登录(" . $password . ")";            $data['status'] = 0;            $memberlogs->savelog($data);            return json(["status" => 1, "msg" => "账号或密码错误"]);        } else {            if ($member->state == '0') {                /**登录日志**/                $data['userid'] = $member->id;                $data['username'] = $username;                $data['memo'] = "帐号禁用中";                $data['status'] = 0;                $memberlogs->savelog($data);                return json(["status" => 1, "msg" => "帐号禁用中"]);            }            $mpassword = $member->password;            if ($mpassword == md5($password)) {                /**登录日志**/                $data['userid'] = $member->id;                $data['username'] = $username;                $data['memo'] = "登录成功";                $data['status'] = 1;                $memberlogs->savelog($data);                $token = self::createTokenByTel($member->id, $username);                Cache::set('login.data.' . $username, $token, 86400 * 15); //将此token保存15天存活期                $member->logintime = date("Y-m-d H:i:s");                $member->token = $token;                $member->save();                if ($this->imageKey) {                    Cache::delete($this->imageKey);                }                $msg = '登录成功';                if (!$this->checkPassword($password)) {                    $msg = '登录成功,密码过于简单请尽快重置';                }                Cache::delete($cacheKey);                Cache::delete($lastAttemptTimeKey);                return json(["status" => 0, "msg" => $msg . $request->param('client_id'), "xtoken" => $token, 'userid' => $member->id, 'picImg' => $member->picImg]);            } else {                Cache::inc($cacheKey, 1);                Cache::set($lastAttemptTimeKey, time());                /**登录日志**/                $data['userid'] = $member->id;                $data['username'] = $member->username;                $data['memo'] = "密码错误";                $data['status'] = 0;                $memberlogs->savelog($data);                return json(["status" => 1, "msg" => "账号或密码错误"]);            }        }    }

通过代码其实可以发现在登录成功之后调用了一个方法为createTokenByTel根据名字可以看到其实就是创建token的方法,我们跟入查看如何创建的。

一次代码审计项目案例

还是很经典的,果然弱口令才是永远的0day!,其实我们通过这个伪造了jwt令牌之后就可以来进行尝试审计后台漏洞了,全局搜索上传方法。

下面这个代码是案例相关代码,不想看直接跳过就行,下面定位有问题的点。

public function multi($ci){        $pathArr = [];        // 获取表单上传文件 例如上传了001.jpg        $isFile = request()->file();        if (empty($isFile) || !isset($isFile[$ci['name']]) || empty($isFile[$ci['name']])) {            return Service::error('上传文件不能空');        }        $files = request()->file($ci['name']);        // 验证,移动到框架应用根目录/uploads/ 目录下        foreach ($files as $file) {            // 返回大小的具体信息            if ($file->getInfo('size') > $ci['size']) {                return Service::error(sprintf("单文件大小不能超出 %s MB", $ci['size'] / 1024 / 1024));            }            $info = $file->validate(                [                    'size' => $ci['size'],                    'ext' => $ci['ext'],                    'type' => $ci['type'],                ]            )                ->rule('md5')                ->move($ci['path']);            if ($info) {                $path = str_replace('\', '/', sprintf("////%s/%s", $ci['path'], $info->getSaveName()));                // 图片压缩                if ($file->getInfo('size') > 0.1 * 1024 * 1024) {                    self::mkThumbnail($path, input('max_size') ?? $this->img_max_width);                }                // 成功上传后 获取上传信息                $pathArr [] = [                    'path' => $path,                    'md5' => $info->md5(),                    'name' => $info->getInfo('name'),                    'size' => $info->getInfo('size'),                ];            } else {                // 上传失败获取错误信息                return Service::error($file->getError());            }        }        return Service::success($pathArr);    }

其实没有什么过滤的地方,所以我们要看哪里调用了方法multi方法。

一次代码审计项目案例

其实还是很明显的,直接的一个文件上传,导致的任意文件上传,这里就不贴出来图片了

二、完结

完结啦!还是非常不错的,可以吃个KFC了

一次代码审计项目案例

作者本人联系方式!

一次代码审计项目案例

原文始发于微信公众号(进击安全):一次代码审计项目案例

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月31日09:50:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   一次代码审计项目案例https://cn-sec.com/archives/3336055.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息