Skip to content

Commit e638c3d

Browse files
committed
Land rapid7#3058 - Prevent jsobfu from generating reserved js keywords
2 parents e30238f + 2a87973 commit e638c3d

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

lib/rex/exploitation/jsobfu.rb

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# -*- coding: binary -*-
22

33
require 'rex/text'
4+
require 'rex/random_identifier_generator'
45
require 'rkelly'
56

67
module Rex
@@ -47,6 +48,15 @@ module Exploitation
4748
#
4849
class JSObfu
4950

51+
# these keywords should never be used as a random var name
52+
# source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words
53+
RESERVED_KEYWORDS = %w(
54+
break case catch continue debugger default delete do else finally
55+
for function if in instanceof new return switch this throw try
56+
typeof var void while with class enum export extends import super
57+
implements interface let package private protected public static yield
58+
)
59+
5060
#
5161
# Abstract Syntax Tree generated by RKelly::Parser#parse
5262
#
@@ -60,6 +70,11 @@ def initialize(code)
6070
@funcs = {}
6171
@vars = {}
6272
@debug = false
73+
@rand_gen = Rex::RandomIdentifierGenerator.new(
74+
:max_length => 15,
75+
:first_char_set => Rex::Text::Alpha+"_$",
76+
:char_set => Rex::Text::AlphaNumeric+"_$"
77+
)
6378
end
6479

6580
#
@@ -107,8 +122,23 @@ def obfuscate
107122
obfuscate_r(@ast)
108123
end
109124

125+
# @return [String] a unique random var name that is not a reserved keyword
126+
def random_var_name
127+
loop do
128+
text = random_string
129+
unless @vars.has_value?(text) or RESERVED_KEYWORDS.include?(text)
130+
return text
131+
end
132+
end
133+
end
134+
110135
protected
111136

137+
# @return [String] a random string
138+
def random_string
139+
@rand_gen.generate
140+
end
141+
112142
#
113143
# Recursive method to obfuscate the given +ast+.
114144
#
@@ -152,14 +182,12 @@ def obfuscate_r(ast)
152182
# Variables
153183
when ::RKelly::Nodes::VarDeclNode
154184
if @vars[node.name].nil?
155-
#@vars[node.name] = "var_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.name}"
156-
@vars[node.name] = "#{Rex::Text.rand_text_alpha(3+rand(12))}"
185+
@vars[node.name] = random_var_name
157186
end
158187
node.name = @vars[node.name]
159188
when ::RKelly::Nodes::ParameterNode
160189
if @vars[node.value].nil?
161-
#@vars[node.value] = "param_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}"
162-
@vars[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}"
190+
@vars[node.value] = random_var_name
163191
end
164192
node.value = @vars[node.value]
165193
when ::RKelly::Nodes::ResolveNode
@@ -181,8 +209,7 @@ def obfuscate_r(ast)
181209
# Functions can also act as objects, so store them in the vars
182210
# and the functions list so we can replace them in both places
183211
if @funcs[node.value].nil? and not @funcs.values.include?(node.value)
184-
#@funcs[node.value] = "func_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}"
185-
@funcs[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}"
212+
@funcs[node.value] = random_var_name
186213
if @vars[node.value].nil?
187214
@vars[node.value] = @funcs[node.value]
188215
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require 'spec_helper'
2+
require 'rex/exploitation/jsobfu'
3+
4+
describe Rex::Exploitation::JSObfu do
5+
6+
subject(:jsobfu) do
7+
described_class.new("")
8+
end
9+
10+
describe '#random_var_name' do
11+
subject(:random_var_name) { jsobfu.random_var_name }
12+
13+
it { should be_a String }
14+
it { should_not be_empty }
15+
16+
it 'is composed of _, $, alphanumeric chars' do
17+
20.times { expect(jsobfu.random_var_name).to match(/\A[a-zA-Z0-9$_]+\Z/) }
18+
end
19+
20+
it 'does not start with a number' do
21+
20.times { expect(jsobfu.random_var_name).not_to match(/\A[0-9]/) }
22+
end
23+
24+
context 'when a reserved word is generated' do
25+
let(:reserved) { described_class::RESERVED_KEYWORDS.first }
26+
let(:random) { 'abcdef' }
27+
let(:generated) { [reserved, reserved, reserved, random] }
28+
29+
before do
30+
jsobfu.stub(:random_string) { generated.shift }
31+
end
32+
33+
it { should be random }
34+
end
35+
36+
context 'when a non-unique random var is generated' do
37+
let(:preexisting) { 'preexist' }
38+
let(:random) { 'abcdef' }
39+
let(:vars) { { 'jQuery' => preexisting } }
40+
let(:generated) { [preexisting, preexisting, preexisting, random] }
41+
42+
before do
43+
jsobfu.stub(:random_string) { generated.shift }
44+
jsobfu.instance_variable_set("@vars", vars)
45+
end
46+
47+
it { should be random }
48+
end
49+
end
50+
51+
end

0 commit comments

Comments
 (0)