Skip to content

Commit d32f93f

Browse files
committed
Add tests for RestClient AppSec instrumentation
1 parent ea7614c commit d32f93f

File tree

7 files changed

+210
-1
lines changed

7 files changed

+210
-1
lines changed

Matrixfile

+3
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,9 @@
303303
'appsec:excon' => {
304304
'excon-latest' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
305305
},
306+
'appsec:rest_client' => {
307+
'http' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
308+
},
306309
'di:active_record' => {
307310
'rails61-mysql2' => '❌ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ jruby',
308311
},

Rakefile

+3-1
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ namespace :spec do
286286
:graphql,
287287
:faraday,
288288
:excon,
289+
:rest_client,
289290
:integration
290291
]
291292

@@ -314,7 +315,8 @@ namespace :spec do
314315
:devise,
315316
:graphql,
316317
:faraday,
317-
:excon
318+
:excon,
319+
:rest_client
318320
].each do |contrib|
319321
desc '' # "Explicitly hiding from `rake -T`"
320322
RSpec::Core::RakeTask.new(contrib) do |t, args|

appraisal/ruby-3.3.rb

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@
191191
gem 'devise', '~> 4.9'
192192
gem 'faraday', '~> 2.0'
193193
gem 'excon', '~> 1.2'
194+
gem 'rest-client'
194195
gem 'rack', '~> 2'
195196
gem 'rack-contrib', '~> 2'
196197
gem 'rack-test' # Dev dependencies for testing rack-based code

gemfiles/ruby_3.3_rails_app.gemfile

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gemfiles/ruby_3.3_rails_app.gemfile.lock

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# frozen_string_literal: true
2+
3+
require 'datadog/tracing/contrib/support/spec_helper'
4+
require 'datadog/appsec/spec_helper'
5+
require 'rack/test'
6+
7+
require 'rest_client'
8+
require 'datadog/tracing'
9+
require 'datadog/appsec'
10+
11+
RSpec.describe 'RestClient SSRF Injection' do
12+
include Rack::Test::Methods
13+
14+
before do
15+
Datadog.configure do |c|
16+
c.tracing.enabled = true
17+
c.tracing.instrument :rack
18+
c.tracing.instrument :http
19+
20+
c.appsec.enabled = true
21+
c.appsec.instrument :rack
22+
c.appsec.instrument :rest_client
23+
24+
c.appsec.ruleset = {
25+
rules: [
26+
{
27+
id: 'rasp-934-100',
28+
name: 'Server-side request forgery exploit',
29+
tags: {
30+
type: 'ssrf',
31+
category: 'vulnerability_trigger',
32+
cwe: '918',
33+
capec: '1000/225/115/664',
34+
confidence: '0',
35+
module: 'rasp'
36+
},
37+
conditions: [
38+
{
39+
parameters: {
40+
resource: [{ address: 'server.io.net.url' }],
41+
params: [
42+
{ address: 'server.request.query' },
43+
]
44+
},
45+
operator: 'ssrf_detector'
46+
}
47+
],
48+
transformers: [],
49+
on_match: ['block']
50+
}
51+
]
52+
}
53+
54+
c.remote.enabled = false
55+
end
56+
57+
allow_any_instance_of(Datadog::Tracing::Transport::HTTP::Client).to receive(:send_request)
58+
59+
stub_request(:get, 'http://example.com').to_return(status: 200, body: 'OK')
60+
end
61+
62+
after do
63+
Datadog.configuration.reset!
64+
Datadog.registry[:rack].reset_configuration!
65+
end
66+
67+
let(:app) do
68+
stack = Rack::Builder.new do
69+
use Datadog::Tracing::Contrib::Rack::TraceMiddleware
70+
use Datadog::AppSec::Contrib::Rack::RequestMiddleware
71+
72+
map '/ssrf' do
73+
run(
74+
lambda do |env|
75+
request = Rack::Request.new(env)
76+
response = RestClient.get("http://#{request.params['url']}")
77+
78+
[200, { 'Content-Type' => 'application/json' }, [response.code]]
79+
end
80+
)
81+
end
82+
end
83+
84+
stack.to_app
85+
end
86+
87+
let(:http_service_entry_span) do
88+
Datadog::Tracing::Transport::TraceFormatter.format!(trace)
89+
spans.find { |s| s.name == 'rack.request' }
90+
end
91+
92+
context 'when request params contain SSRF attack' do
93+
before do
94+
get('/ssrf', { 'url' => '169.254.169.254' }, { 'REMOTE_ADDR' => '127.0.0.1' })
95+
end
96+
97+
it { expect(last_response).to be_forbidden }
98+
end
99+
100+
context 'when request params do not contain SSRF attack' do
101+
before do
102+
get('/ssrf', { 'url' => 'example.com' }, { 'REMOTE_ADDR' => '127.0.0.1' })
103+
end
104+
105+
it { expect(last_response).to be_ok }
106+
end
107+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
require 'datadog/appsec/spec_helper'
4+
require 'rest_client'
5+
6+
RSpec.describe 'RestClient::Request patch for SSRF detection' do
7+
let(:context) { instance_double(Datadog::AppSec::Context, run_rasp: waf_response) }
8+
let(:waf_response) { instance_double(Datadog::AppSec::SecurityEngine::Result::Ok, match?: false) }
9+
10+
let(:request_url) { 'http://example.com/success' }
11+
12+
before do
13+
Datadog.configure do |c|
14+
c.appsec.enabled = true
15+
c.appsec.instrument :rest_client
16+
end
17+
18+
allow(Datadog::AppSec).to receive(:active_context).and_return(context)
19+
20+
WebMock.disable_net_connect!(allow: agent_url)
21+
WebMock.enable!(allow: agent_url)
22+
23+
stub_request(:get, request_url).to_return(status: 200, body: 'OK')
24+
end
25+
26+
after do
27+
Datadog.configuration.reset!
28+
end
29+
30+
context 'when RASP is disabled' do
31+
before do
32+
allow(Datadog::AppSec).to receive(:rasp_enabled?).and_return(false)
33+
end
34+
35+
it 'does not call waf when making a request' do
36+
expect(Datadog::AppSec.active_context).not_to receive(:run_rasp)
37+
38+
RestClient.get(request_url)
39+
end
40+
end
41+
42+
context 'when there is no active context' do
43+
let(:context) { nil }
44+
45+
it 'does not call waf when making a request' do
46+
expect(Datadog::AppSec.active_context).not_to receive(:run_rasp)
47+
48+
RestClient.get(request_url)
49+
end
50+
end
51+
52+
context 'when RASP is enabled' do
53+
before do
54+
allow(Datadog::AppSec).to receive(:rasp_enabled?).and_return(true)
55+
end
56+
57+
it 'calls waf with correct arguments when making a request' do
58+
expect(Datadog::AppSec.active_context).to(
59+
receive(:run_rasp).with(
60+
Datadog::AppSec::Ext::RASP_SSRF,
61+
{},
62+
{ 'server.io.net.url' => 'http://example.com/success' },
63+
Datadog.configuration.appsec.waf_timeout
64+
)
65+
)
66+
67+
RestClient.get(request_url)
68+
end
69+
70+
it 'returns the http response' do
71+
response = RestClient.get(request_url)
72+
73+
expect(response.code).to eq(200)
74+
expect(response.body).to eq('OK')
75+
end
76+
end
77+
end

0 commit comments

Comments
 (0)