ssrf_proxy 0.0.2
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/bin/ssrf-proxy +227 -0
- data/bin/ssrf-scan +452 -0
- data/lib/ssrf_proxy.rb +18 -0
- data/lib/ssrf_proxy/http.rb +1306 -0
- data/lib/ssrf_proxy/server.rb +147 -0
- data/lib/ssrf_proxy/version.rb +10 -0
- metadata +201 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2015 Brendan Coles <bcoles@gmail.com>
|
|
4
|
+
# SSRF Proxy - https://github.com/bcoles/ssrf_proxy
|
|
5
|
+
# See the file 'LICENSE' for copying permission
|
|
6
|
+
#
|
|
7
|
+
|
|
8
|
+
require "ssrf_proxy"
|
|
9
|
+
|
|
10
|
+
module SSRFProxy
|
|
11
|
+
#
|
|
12
|
+
# @note SSRFProxy::Server
|
|
13
|
+
#
|
|
14
|
+
class Server
|
|
15
|
+
|
|
16
|
+
# @note output status messages
|
|
17
|
+
def print_status(msg='')
|
|
18
|
+
puts '[*] '.blue + msg
|
|
19
|
+
end
|
|
20
|
+
# @note output progress messages
|
|
21
|
+
def print_good(msg='')
|
|
22
|
+
puts '[+] '.green + msg
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
require 'socket'
|
|
26
|
+
require 'celluloid/current'
|
|
27
|
+
require 'celluloid/io'
|
|
28
|
+
|
|
29
|
+
include Celluloid::IO
|
|
30
|
+
finalizer :shutdown
|
|
31
|
+
|
|
32
|
+
attr_accessor :logger
|
|
33
|
+
|
|
34
|
+
# @note logger
|
|
35
|
+
def logger
|
|
36
|
+
@logger || ::Logger.new(STDOUT).tap do |log|
|
|
37
|
+
log.progname = 'ssrf-proxy-server'
|
|
38
|
+
log.level = ::Logger::WARN
|
|
39
|
+
log.datetime_format = '%Y-%m-%d %H:%M:%S '
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# @note SSRFProxy::Server errors
|
|
45
|
+
#
|
|
46
|
+
module Error
|
|
47
|
+
# custom errors
|
|
48
|
+
class Error < StandardError; end
|
|
49
|
+
exceptions = %w( InvalidSsrf )
|
|
50
|
+
exceptions.each { |e| const_set(e, Class.new(Error)) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#
|
|
54
|
+
# @note Start the local server and listen for connections
|
|
55
|
+
#
|
|
56
|
+
# @options
|
|
57
|
+
# - interface - String - Listen interface (Default: 127.0.0.1)
|
|
58
|
+
# - port - Integer - Listen port (Default: 8081)
|
|
59
|
+
# - ssrf - SSRFProxy::HTTP - SSRF
|
|
60
|
+
#
|
|
61
|
+
def initialize(interface='127.0.0.1', port=8081, ssrf)
|
|
62
|
+
@logger = ::Logger.new(STDOUT).tap do |log|
|
|
63
|
+
log.progname = 'ssrf-proxy-server'
|
|
64
|
+
log.level = ::Logger::WARN
|
|
65
|
+
log.datetime_format = '%Y-%m-%d %H:%M:%S '
|
|
66
|
+
end
|
|
67
|
+
# set ssrf
|
|
68
|
+
unless ssrf.class == SSRFProxy::HTTP
|
|
69
|
+
raise SSRFProxy::Server::Error::InvalidSsrf.new,
|
|
70
|
+
"Invalid SSRF provided"
|
|
71
|
+
end
|
|
72
|
+
@ssrf = ssrf
|
|
73
|
+
# start server
|
|
74
|
+
logger.info "Starting HTTP proxy on #{interface}:#{port}"
|
|
75
|
+
print_status "Listening on #{interface}:#{port}"
|
|
76
|
+
@server = TCPServer.new(interface, port.to_i)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# @note Run proxy server
|
|
81
|
+
#
|
|
82
|
+
def serve
|
|
83
|
+
loop { async.handle_connection(@server.accept) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
#
|
|
89
|
+
# @note Handle shutdown of client socket
|
|
90
|
+
#
|
|
91
|
+
def shutdown
|
|
92
|
+
logger.info 'Shutting down'
|
|
93
|
+
@server.close if @server
|
|
94
|
+
logger.debug 'Shutdown complete'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
#
|
|
98
|
+
# @note Handle client request
|
|
99
|
+
#
|
|
100
|
+
# @options
|
|
101
|
+
# - socket - String - client socket
|
|
102
|
+
#
|
|
103
|
+
def handle_connection(socket)
|
|
104
|
+
_, port, host = socket.peeraddr
|
|
105
|
+
max_len = 4096
|
|
106
|
+
logger.debug "Client #{host}:#{port} connected"
|
|
107
|
+
request = socket.readpartial(max_len)
|
|
108
|
+
logger.debug("Received client request (#{request.length} bytes):\n#{request}")
|
|
109
|
+
if request.length >= max_len
|
|
110
|
+
logger.warn("Client request too long (truncated at #{request.length} bytes)")
|
|
111
|
+
end
|
|
112
|
+
if request.to_s !~ /\A[A-Z]{1,20} /
|
|
113
|
+
logger.warn("Malformed client HTTP request")
|
|
114
|
+
response = "HTTP/1.0 501 Error\r\n\r\n"
|
|
115
|
+
elsif request.to_s =~ /\ACONNECT ([a-zA-Z0-9\.\-]+:[\d]+) .*$/
|
|
116
|
+
host = "#{$1}"
|
|
117
|
+
logger.info("Negotiating connection to #{host}")
|
|
118
|
+
response = @ssrf.send_request("GET http://#{host}/ HTTP/1.0\n\n")
|
|
119
|
+
if response =~ /^Server: SSRF Proxy$/i && response =~ /^Content-Length: 0$/i
|
|
120
|
+
logger.warn("Connection to #{host} failed")
|
|
121
|
+
response = "HTTP/1.0 502 Bad Gateway\r\n\r\n"
|
|
122
|
+
else
|
|
123
|
+
logger.info("Connected to #{host} successfully")
|
|
124
|
+
socket.write("HTTP/1.0 200 Connection established\r\n\r\n")
|
|
125
|
+
request = socket.readpartial(max_len)
|
|
126
|
+
logger.debug("Received client request (#{request.length} bytes):\n#{request}")
|
|
127
|
+
if request.length >= max_len
|
|
128
|
+
logger.warn("Client request too long (truncated at #{request.length} bytes)")
|
|
129
|
+
end
|
|
130
|
+
response = @ssrf.send_request(request)
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
response = @ssrf.send_request(request)
|
|
134
|
+
end
|
|
135
|
+
socket.write(response)
|
|
136
|
+
socket.close
|
|
137
|
+
rescue EOFError, Errno::ECONNRESET
|
|
138
|
+
logger.debug "Client #{host}:#{port} disconnected"
|
|
139
|
+
socket.close
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private :shutdown,:handle_connection
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
metadata
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ssrf_proxy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.2
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Brendan Coles
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2015-10-02 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: bundler
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '1.8'
|
|
22
|
+
type: :development
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.8'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: rake
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ~>
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '10.0'
|
|
38
|
+
type: :development
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ~>
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '10.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: bundler-audit
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ! '>='
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: logger
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ! '>='
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0'
|
|
70
|
+
type: :runtime
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ! '>='
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
- !ruby/object:Gem::Dependency
|
|
79
|
+
name: colorize
|
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ! '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
type: :runtime
|
|
87
|
+
prerelease: false
|
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
89
|
+
none: false
|
|
90
|
+
requirements:
|
|
91
|
+
- - ! '>='
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: webrick
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ! '>='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
type: :runtime
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - ! '>='
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: celluloid
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
none: false
|
|
114
|
+
requirements:
|
|
115
|
+
- - ! '>='
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: 0.17.1
|
|
118
|
+
type: :runtime
|
|
119
|
+
prerelease: false
|
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
121
|
+
none: false
|
|
122
|
+
requirements:
|
|
123
|
+
- - ! '>='
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: 0.17.1
|
|
126
|
+
- !ruby/object:Gem::Dependency
|
|
127
|
+
name: celluloid-io
|
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
|
129
|
+
none: false
|
|
130
|
+
requirements:
|
|
131
|
+
- - ! '>='
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: 0.17.1
|
|
134
|
+
type: :runtime
|
|
135
|
+
prerelease: false
|
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
137
|
+
none: false
|
|
138
|
+
requirements:
|
|
139
|
+
- - ! '>='
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: 0.17.1
|
|
142
|
+
- !ruby/object:Gem::Dependency
|
|
143
|
+
name: ipaddress
|
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
|
145
|
+
none: false
|
|
146
|
+
requirements:
|
|
147
|
+
- - ! '>='
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: '0'
|
|
150
|
+
type: :runtime
|
|
151
|
+
prerelease: false
|
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
153
|
+
none: false
|
|
154
|
+
requirements:
|
|
155
|
+
- - ! '>='
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '0'
|
|
158
|
+
description: SSRF Proxy is a multi-threaded HTTP proxy server designed to tunnel client
|
|
159
|
+
HTTP traffic through HTTP servers vulnerable to HTTP Server-Side Request Forgery
|
|
160
|
+
(SSRF).
|
|
161
|
+
email:
|
|
162
|
+
- bcoles@gmail.com
|
|
163
|
+
executables:
|
|
164
|
+
- ssrf-proxy
|
|
165
|
+
- ssrf-scan
|
|
166
|
+
extensions: []
|
|
167
|
+
extra_rdoc_files: []
|
|
168
|
+
files:
|
|
169
|
+
- lib/ssrf_proxy.rb
|
|
170
|
+
- lib/ssrf_proxy/http.rb
|
|
171
|
+
- lib/ssrf_proxy/server.rb
|
|
172
|
+
- lib/ssrf_proxy/version.rb
|
|
173
|
+
- bin/ssrf-proxy
|
|
174
|
+
- bin/ssrf-scan
|
|
175
|
+
homepage: https://github.com/bcoles/ssrf_proxy
|
|
176
|
+
licenses:
|
|
177
|
+
- MIT
|
|
178
|
+
post_install_message:
|
|
179
|
+
rdoc_options: []
|
|
180
|
+
require_paths:
|
|
181
|
+
- lib
|
|
182
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
183
|
+
none: false
|
|
184
|
+
requirements:
|
|
185
|
+
- - ! '>='
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: 1.9.3
|
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
|
+
none: false
|
|
190
|
+
requirements:
|
|
191
|
+
- - ! '>='
|
|
192
|
+
- !ruby/object:Gem::Version
|
|
193
|
+
version: '0'
|
|
194
|
+
requirements: []
|
|
195
|
+
rubyforge_project:
|
|
196
|
+
rubygems_version: 1.8.23
|
|
197
|
+
signing_key:
|
|
198
|
+
specification_version: 3
|
|
199
|
+
summary: SSRF Proxy facilitates tunneling HTTP communications through servers vulnerable
|
|
200
|
+
to SSRF.
|
|
201
|
+
test_files: []
|