|
$ `) z+ u& L6 @, Q% i2 O. ^$ t& R0 s
前言这个CMS非常适合入门代码审计的人去学习,因为代码简单且漏洞成因经典,对一些新手有学习价值,前台注入从入口开始:/semcms/Templete/default/Include/index.php , K, u! {( J9 m* G
跟进web_inc.php,首先包含1)db_conn.php:建立与数据库的连接,代码量很少也很简单。 3 z3 ?3 R- Y6 I* a. M
2)contorl.php:对$_GET进行全局过滤危险的SQL函数。
3 g4 {" ]7 [! [) J% g V 这个过滤从最简单的角度来说,即mysql<8的情况下,把select禁用了,其实就没办法进行跨表查询,SQL利用造成危害的可能性会大大降低,当然这是一种直接且无需考虑用户体验为原则的暴力做法,点到为止吧。
# q" z, j5 g3 d9 ` 回到web_inc.php,继续阅读,后面吸引我的地方,在于 89 line一处SQL语句的地方。 % _* x! u/ j% ?( ]1 R0 s- I8 Z
可以看到$Language没有单引号,直接拼接到语句中,且值由POST方式传递,不过这里经过了verify_str函数,导致我没有办法利用select进行子查询,获取到sc_user表的后台管理员用户密码,那么事实真的如此么?
: M0 A) w, R6 d. o) D $Language=test_input(verify_str($_POST["languageID"]));经过verify_str函数处理后,会传入test_input函数,其返回值将会拼接进SQL语句中进行查询。 # }) Y. d: o; l9 y3 h1 F1 j9 s. w: {
test_input里面有个有趣的函数stripslashes,函数的作用就是用于去除反斜杠,举个如图例子 9 `1 D8 F& s" x5 p+ z2 v% Z8 e0 {0 W
那么绕过verify_str思路就水到渠成了。 9 r$ e# P9 s5 \2 e6 w+ V- m' m
分析下payload的原理languageID=-1 uni\on sel\ect 1,concat(user_admin,0x2d,user_ps),3,4,5,6,7,8,9,10,11,12,13
/ X, o3 w0 A, \3 j* t8 h ,14 from sc_userun\ion&&sel\ect绕过了verify_str函数的正则匹配,经过test_input的stripslashes去掉反斜杠,最终拼接到数据库中执行的语句,实际上
+ S6 {, \) p7 z# k# f7 V' A 返回的后台管理员的账号密码信息到$tag_indexmetatit变量中。 ' K0 a8 @( D u* }
并经过if判断传递给$indextitle变量,最终直接被echo到返回包if (empty($tag_indexmetatit)){$indextitle=$tag_indexkey;}else{$indextitle=$tag_indexmetatit;}' Q- i8 ~% O: D1 q* K& @
。
) m% p' i9 [/ w/ d. D# y& m9 ] if (empty($tag_prometatit)){$protitle=$tag_prokey;}else{$protitle=$tag_prometatit;}$ D* `( ?8 O7 L& o* c/ r
if (empty($tag_newmetatit)){$newstitle=$tag_newkey;}
b; Y" h6 i/ X4 x; q else{$newstitle=$tag_newmetatit;} 
' U" ?. e4 \6 a0 y7 F6 s) B' x 小结 由于web_inc.php是所有前台文件都会包含的,所以说这个注入点在任意前台文件中都可以无条件触发,唯一的区别就是其他文件可能没有回显的地方当然,同样地基于此绕过原理,还可以找到很多处类似的注入或者其他更为简单且直接的注入点,这些就留给读者们自己探索。
) u W% h, H7 ?( {2 K4 }: S, R 【→所有资源关注我,私信回复“资料”获取←】1、网络安全学习路线2、电子书籍(白帽子)3、安全大厂内部视频4、100份src文档5、常见安全面试题6、ctf大赛经典题目解析7、全套工具包8、应急响应笔记 , H9 _ E. ?" y$ J0 }
寻找后台 虽然在0x01中挖掘到了前台无限制回显的SQL注入漏洞,但因为查询数据库用的是mysqli的query函数而不是multi_query函数,故注入点并不支持堆叠注入,这直接导致我们少了一条SQLGetSHell的道路。 ! W( D* e) N& |
值得开心一点的是,我们目前可以通过注入点获取到管理员的账号密码,不过这个CMS的后台地址安装时是随机生成的,所以找到后台地址很困难,下面是自己尝试寻找后台的失败过程,很可惜没有突破失败的过程semcms/install/index.php ; v, a" n( \3 R$ s" b9 {, S" Q3 J
安装文件有后台地址的生成代码 6 ~8 I' }/ a. h, u
那么我的思路,就是全局定位$ht_filename变量,看看有没有对此进行操作并存储的代码。 " ^: C P3 n) ]* n( n
很遗憾,并没有找到对此变量引用的代码还没到放弃的时候,一般这个时候,我还会额外找找一些其他的办法比如搜索scandir函数,该函数作用是列出指定路径中的文件和目录,目的是通过找到类似目录遍历漏洞的点,从而找到后台地址。 o+ h9 ^( J3 J7 N- F, N, O4 b
继续回溯TemplateDir 1 O% s6 D4 _9 p/ o
可惜的是,发现传入的第一个参数是固定的,故这个思路也断了,暂时没有想到其他的好办法了GetShell思路 目标CMS的代码量并不高,故寻找GetShell的思路,可以采用危险函数定位的方法来进行快速排除并在存在漏洞的可疑的地方再进行回溯分析。
/ A! c6 q! x+ P7 [- S 定位思路文件包含函数:流程控制requireincluderequire_onceinclude_once文件操作函数: 文件系统函数copy — 拷贝文件delete — 参见 unlink 或 unset
j k- n9 X# k- {# {0 l fflush — 将缓冲内容输出到文件file_get_contents — 将整个文件读入一个字符串file_put_contents — 将一个字符串写入文件fputcsv — 将行格式化为 CSV 并写入文件指针
! a' w$ M+ @' g/ ]$ S" M6 E/ P fputs — fwrite 的别名fread — 读取文件(可安全用于二进制文件)fscanf — 从文件中格式化输入fwrite — 写入文件(可安全用于二进制文件)move_uploaded_file — 将上传的文件移动到新位置
4 c# w6 |. k; x; u* L3 J' p readfile — 输出文件rename — 重命名一个文件或目录rmdir — 删除目录unlink — 删除文件代码注入函数:eval — 把字符串作为PHP代码执行assert — 检查一个断言是否为 false
2 j( G; _% |" s preg_replace — 执行一个正则表达式的搜索和替换命令执行函数:程序执行函数escapeshellarg — 把字符串转码为可以在 shell 命令里使用的参数escapeshellcmd — shell 元字符转义
! Y+ a7 T9 a$ f) @- M) U9 O exec — 执行一个外部程序passthru — 执行外部程序并且显示原始输出proc_close — 关闭由 proc_open 打开的进程并且返回进程退出码proc_get_status — 获取由 proc_open 函数打开的进程的信息
; s+ t- C" G2 e0 g. e proc_nice — 修改当前进程的优先级proc_open — 执行一个命令,并且打开用来输入/输出的文件指针proc_terminate — 杀除由 proc_open 打开的进程shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
: s6 ?9 O+ }: _: c; O c0 } system — 执行外部程序,并且显示输出变量覆盖:extract — 从数组中将变量导入到当前的符号表parse_str — 将字符串解析成多个变量后台GetShell搜索file_put_contents ' G8 c* e0 v1 s; H" g" o2 M3 A
函数,只有两个结果,一个是参数写死,故放弃,故只剩这个分析。 2 {0 P- h0 e9 o2 I: L
写入的文件$templateUrl得到的值是固定两种类型../index.php 根目录; g" S8 w' o% c( _
../.htaccess 根目录function Mbapp($mb,$lujin,$mblujin,$dirpaths。 9 w) x2 U! e, e8 y3 x( g- r
,$htmlopen){. k. L7 [+ r' b) l1 i
( D) i2 Z$ _; S/ F- I
$ m4 a. A7 N$ }$ E; K
if ($htmlopen==1){$ml="j";}else{$ml="d";}/ d8 t4 Q& B. T6 [8 o$ n- @( B4 L+ Z
9 W8 e" H! ]( y `3 S& {" d $template="index.php,hta/".$ml H7 {( E% S! o# y, o, c
."/.htaccess"; //开始应用模版3 _- }6 x2 ?2 [4 t/ Y
// 1.$template=index.php,hta/j/.htaccess3 R. v! c t0 d1 S; l9 C2 A
// 2.$template=index.php,hta/d/.htaccess7 I/ n( N1 I, y$ ~; L
" j' D: \$ m. Z
$template_mb=explode(",",$template);
& }6 e& Q8 J0 `% C6 [, y, g. I //$template_mb 根据,分割为index.php和hta/d/.htaccess的数组* Q9 ]; R2 Z; T2 e* G
* d% d0 n% G4 a9 G3 s; l ~8 R
for
- p* D S* l; G& e% k. J3 n ($i=0;$i
# }0 g1 B8 t+ O9 r5 j5 T $mblujin.Templete/.$mb./Include/.$template_mb[$i]);
6 Z- n2 ]2 Q% u // ../拼接$template_mb[$i]中的"hta/".$ml 0 Y/ M! ]/ Q( n
."/"字符串替换为空的结果
! J- P! d" i& b2 j& P // 即得到../.htacess 或者 ../.index.php
0 F5 P3 Y' q5 e' Z $templateUrl = $lujin.str_replace( , p- f% q5 e; h; {. D0 O
"hta/".$ml."/","", $template_mb[$i]);
+ i8 p( d& ] _ // 修改$template_o的标记为$mb的值
9 K0 Q* Y; O6 G$ v8 u7 h
- c- h7 W7 ^" c4 y1 D( _ $output = str_replace(, $mb, $template_o);% N' p0 O) Y! p
$output = str_replace( 8 u( g/ r7 B" y
, $dirpaths, $output);
' l( P( s3 ]/ m+ L* H8 K // 将替换的内容写入到$templateUrl指向的文件
2 R! L; a6 o7 n# _2 x) J file_put_contents($templateUrl 4 b( w" E* j# \: K6 n2 _: I
, $output);& [9 V3 h+ n4 d, g6 T
0 k+ \6 \6 w. O
}! L; a! T( N/ ^; W4 G4 a1 b
3 P6 x% Q2 q, J \; S# `6 z9 f }那么这个函数如果$mb可控的话,会发生什么问题?问题一能够修改semcms/Templete/default/Include/index.php中的
- o T8 ?( _$ ~* ?% b2 [! L 的内容
8 a4 B; [, \$ Q( k) _ 那么可以尝试如下的形式构造payload:/semcms/N8D3ch_Admin/SEMCMS_Template.php?CF=template&mb=default/.phpinfo():./..最终的话会在 ( j0 ?4 v: T% Y Y' |* f3 ~: z
semcms/Templete/default/Include/index.php写入如下图所示。
/ X' {7 H0 g7 W' g$ a( M$ f( u1 z! | 问题2能够修改根目录.htacess的内容与 .htaccess 相关的奇淫技巧SetHandler application/x-httpd-php此时当前目录及其子目录下所有文件都会被当做 php 解析。
0 @: D% l, Y# S6 @1 u 那么可以尝试如下的形式构造payload:/semcms/N8D3ch_Admin/SEMCMS_Template.php?CF=template&mb=default/%0aSetHandler%20application/ - c- u# A; S; b1 P4 q/ _2 a
x-httpd-php%0a%23/../..3 o4 l5 U( I) j7 X
! G# S& G# G* |* `
//这里因为application/x-httpd-php中带有/,所以多需要一个../进行跳转 $ D# P' c" M$ O0 g7 V
最终写入的内容: ! { ~6 h2 a- A5 K' D
那么我们随意上传一个文件,即可当作PHP来解析。那么$mb到底是否可控呢? 回溯Mbapp函数的上层调用,可以发现可以通过$_GET[mb]来控制。 0 k) A0 M8 s$ ~$ @, _: r
不过因为文件引进/semcms/Include/contorl.php,会调用verify_str对$_GET变量进行过滤。 . U: O7 j7 v" ]8 `! [& u
很不凑巧,过滤了单引号,导致我们问题1覆盖的index.php的思路直接断了,因为根本没办法逃逸出单引号不过问题2的话,倒是可以成功,因为传入的内容并不在inject_check_sql的黑名单中,可以成功地覆盖。 V% j- F4 t H6 O/ T" X2 A. \
.htaccess文件,不过这种方式也是有局限性的,需要Apahce是通过module的形式加载PHP的文件来执行才可以,并且需要在Linux环境,因为window不支持跨越不存在的路径任意文件删除 最后还想额外提一下关于后台的漏洞,便是其中一个任意文件删除漏洞,这个删除点不是直接的点,而是先通过构造需要删除的文件路径存进数据库,再通过触发其他点进行获取,传入 ^" U* G4 N- w/ Z- g
unlink中进行删除,这种类型笔者称之为二次任意文件删除漏洞,很是经典。漏洞演示:1)传入../rmme.txt作为图片的路径 ! `# E5 [! X# i/ H
2)选择删除图片后,会删除文件网站根目录下的rmme.txt文件 6 c- X" F1 c( E/ l- |( J n
成因 1) 添加URL入库的时候,只是做了test_input,并没有过滤..。 8 t8 w4 _2 a2 {: T8 U+ S# a
(2) 直接入库 
: b& [, t. o( J3 e- T (3) 删除图片的时候,传入AID,获取到images_url字段的值../rmme.txt传入Delfile函数进行删除。 1 d4 n; C2 T+ V/ e7 G% U+ U; {
Delfile函数先判断文件是否存在,再使用unlink删掉文件,全程没有一丁点的过滤,送分题! # T. A v" |; W' e7 }6 J
总结 本文直接从一个入口的注入点展开,想找到一条合适的链路到GetShell的完整过程,但是遗憾的是,没能解决6位随机后台地址的问题,故实际利用起来的话,局限性还是有的,姑且称之为一次分享式的尝试性代码审计体验录吧。 7 P' U2 u4 t2 f6 `4 q
V# h# W- w" R0 a- I6 D1 ?
2 _2 g! R, ^% x( S7 \' K7 L4 ]
; P8 ~2 Y6 D7 F# B
; C: R3 l6 E, Q6 m |