Skip to content

Commit 520ac7e

Browse files
committed
TeamCity: Correctly encrypt UTF-8 codepoints
1 parent 2073121 commit 520ac7e

File tree

2 files changed

+41
-24
lines changed

2 files changed

+41
-24
lines changed

lib/metasploit/framework/login_scanner/teamcity.rb

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,47 @@ def pkcs1pad2(text, n)
1616
raise ArgumentError, "Cannot pad the text: '#{text.inspect}'" unless text.is_a?(String)
1717
raise ArgumentError, "Invalid message length: '#{n.inspect}'" unless n.is_a?(Integer)
1818

19-
if n < text.length + 11
19+
bytes_per_char = two_byte_chars?(text) ? 2 : 1
20+
if n < ((bytes_per_char * text.length) + 11)
2021
raise ArgumentError, 'Message too long'
2122
end
2223

23-
r = Array.new(n, 0)
24+
ba = Array.new(n, 0)
2425
n -= 1
25-
r[n] = text.length
26+
ba[n] = text.length
2627

2728
i = text.length - 1
2829

2930
while i >= 0 && n > 0
30-
c = text[i].ord
31+
char_code = text[i].ord
3132
i -= 1
32-
n -= 1
33-
r[n] = c % 0x100
33+
34+
num_bytes = bytes_per_char
35+
36+
while num_bytes > 0
37+
next_byte = char_code % 0x100
38+
char_code >>= 8
39+
40+
n -= 1
41+
ba[n] = next_byte
42+
43+
num_bytes -= 1
44+
end
3445
end
3546
n -= 1
36-
r[n] = 0
47+
ba[n] = 0
3748

3849
while n > 2
3950
n -= 1
40-
r[n] = rand(1..255) # Can't be a null byte.
51+
ba[n] = rand(1..255) # Can't be a null byte.
4152
end
4253

4354
n -= 1
44-
r[n] = 2
55+
ba[n] = 2
4556
n -= 1
46-
r[n] = 0
57+
ba[n] = 0
4758

48-
r.pack("C*").unpack1("H*").to_i(16)
59+
ba.pack("C*").unpack1("H*").to_i(16)
4960
end
5061

5162
# @param [String] modulus
@@ -66,7 +77,11 @@ def rsa_encrypt(modulus, exponent, text)
6677
def two_byte_chars?(str)
6778
raise ArgumentError, 'Unable to check char size for non-string value' unless str.is_a?(String)
6879

69-
str.bytesize > str.length
80+
str.each_codepoint do |codepoint|
81+
return true if codepoint >> 8 > 0
82+
end
83+
84+
false
7085
end
7186

7287
def max_data_size(str)
@@ -85,13 +100,14 @@ def encrypt_data(text, public_key)
85100

86101
exponent = '10001'
87102
e = []
88-
g = max_data_size(text)
103+
utf_text = text.dup.force_encoding(::Encoding::UTF_8)
104+
g = max_data_size(utf_text)
89105

90106
c = 0
91-
while c < text.length
92-
b = [text.length, c + g].min
107+
while c < utf_text.length
108+
b = [utf_text.length, c + g].min
93109

94-
a = text[c..b]
110+
a = utf_text[c..b]
95111

96112
encrypt = rsa_encrypt(public_key, exponent, a)
97113
e.push(encrypt)

spec/modules/auxiliary/scanner/teamcity/teamcity_login_spec.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
[
1313
{ input: 'abc', expected: false },
1414
{ input: '', expected: false },
15-
{ input: 'ççç', expected: true },
16-
{ input: 'メタスプライトが大好きです', expected: true } # I love metasploit
15+
{ input: 'ççç', expected: false }, # has2byteChars('ç') -> false
16+
# I love metasploit
17+
{ input: 'メタスプライトが大好きです', expected: true } # has2byteChars('メタスプライトが大好きです') -> true
1718
].each do |scenario|
1819
it 'returns the correct value' do
1920
expect(subject.two_byte_chars?(scenario[:input])).to eq(scenario[:expected])
@@ -36,7 +37,7 @@
3637
[
3738
{ input: 'abc', expected: 116 },
3839
{ input: '', expected: 116 },
39-
{ input: 'ççç', expected: 58 }, # TODO: In the browser, this is reported as 118.
40+
{ input: 'ççç', expected: 116 },
4041
{ input: 'メタスプライトが大好きです', expected: 58 } # I love metasploit
4142
].each do |scenario|
4243
it 'returns the correct maximum message length' do
@@ -58,16 +59,16 @@
5859

5960
describe '#pkcs1pad2' do
6061
[
61-
{ input: 'abc', expected: '0061626303' },
62-
{ input: '', expected: '0000' },
63-
{ input: 'ççç', expected: '00e7e7e703' }, # 3 chars, E7 codepoint
64-
{ input: 'メタスプライトが大好きです', expected: '0030e130bf30b930d730e930a430c8304c5927597d304d3067305913' } # I love metasploit
62+
{ input: 'abc', expected: /0061626303$/ },
63+
{ input: '', expected: /0000$/ },
64+
{ input: 'ççç', expected: /00e7e7e703$/ }, # 3 chars, E7 codepoint
65+
{ input: 'メタスプライトが大好きです', expected: /0030e130bf30b930d730e930a430c8304c5927597d304d306730590d$/ } # I love metasploit
6566
].each do |scenario|
6667
it 'correctly pads text' do
6768
n = (teamcity_public_key.bit_length + 7) >> 3
6869
padded_as_big_int = subject.pkcs1pad2(scenario[:input], n)
6970
padded_hex = padded_as_big_int.to_s(16)
70-
expect(padded_hex.end_with?(scenario[:expected])).to eql(true)
71+
expect(padded_hex).to match(scenario[:expected])
7172
end
7273
end
7374

0 commit comments

Comments
 (0)