Skip to content

Commit 62addb8

Browse files
ssrf: fix post-request effective hostname handling + harden tests (#377)
1 parent 37fabf1 commit 62addb8

File tree

11 files changed

+157
-12
lines changed

11 files changed

+157
-12
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,25 @@ Prerequisites:
3939

4040
##### x86_64
4141
```
42-
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.x86_64.rpm
42+
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.x86_64.rpm
4343
```
4444

4545
##### arm64 / aarch64
4646
```
47-
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.aarch64.rpm
47+
rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.aarch64.rpm
4848
```
4949

5050
#### For Debian-based Systems (Debian, Ubuntu)
5151

5252
##### x86_64
5353
```
54-
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.x86_64.deb
54+
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.x86_64.deb
5555
dpkg -i -E ./aikido-php-firewall.x86_64.deb
5656
```
5757

5858
##### arm64 / aarch64
5959
```
60-
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.aarch64.deb
60+
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.aarch64.deb
6161
dpkg -i -E ./aikido-php-firewall.aarch64.deb
6262
```
6363

docs/aws-elastic-beanstalk.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
```
55
commands:
66
aikido-php-firewall:
7-
command: "rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.x86_64.rpm"
7+
command: "rpm -Uvh --oldpackage https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.x86_64.rpm"
88
ignoreErrors: true
99
1010
files:

docs/fly-io.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Create a script to install the Aikido PHP Firewall during deployment:
3232
#!/usr/bin/env bash
3333
cd /tmp
3434

35-
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.x86_64.deb
35+
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.x86_64.deb
3636
dpkg -i -E ./aikido-php-firewall.x86_64.deb
3737
```
3838

docs/laravel-forge.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ You can get your token from the [Aikido Security Dashboard](https://help.aikido.
88

99
Go to "Commands" and run the following by replacing the sudo password with the one that Forge displays when the server is created:
1010
```
11-
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.12/aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S dpkg -i -E ./aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S service php8.4-fpm restart
11+
curl -L -O https://github.com/AikidoSec/firewall-php/releases/download/v1.4.13/aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S dpkg -i -E ./aikido-php-firewall.x86_64.deb && echo "YOUR_SUDO_PASSWORD_HERE" | sudo -S service php8.4-fpm restart
1212
```
1313

1414
![Forge Commands](./forge-commands.png)

lib/agent/constants/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package constants
22

33
const (
4-
Version = "1.4.12"
4+
Version = "1.4.13"
55
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
66
PidPath = "/run/aikido-" + Version + "/aikido-agent.pid"
77
ConfigUpdatedAtMethod = "GET"

lib/php-extension/include/php_aikido.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
extern zend_module_entry aikido_module_entry;
44
#define phpext_aikido_ptr &aikido_module_entry
55

6-
#define PHP_AIKIDO_VERSION "1.4.12"
6+
#define PHP_AIKIDO_VERSION "1.4.13"
77

88
#if defined(ZTS) && defined(COMPILE_DL_AIKIDO)
99
ZEND_TSRMLS_CACHE_EXTERN()

lib/request-processor/globals/globals.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ func CreateServer(token string) *ServerData {
6262
}
6363

6464
const (
65-
Version = "1.4.12"
65+
Version = "1.4.13"
6666
SocketPath = "/run/aikido-" + Version + "/aikido-agent.sock"
6767
)

lib/request-processor/vulnerabilities/ssrf/checkContextForSSRF.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ func CheckEffectiveHostnameForSSRF(effectiveHostname string) *utils.InterceptorR
8181
// Hostname was found in user input and the effective hostname (after redirects) resolved to a private IP address -> SSRF
8282
interceptorResult.Metadata["isPrivateIp"] = "true"
8383
}
84+
return interceptorResult
8485
}
8586

86-
return interceptorResult
87+
return nil
8788
}
8889

8990
/* This is called after the request is made to check for SSRF in the resolvedIP - IP optained from the PHP library that made the request (curl) */
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package ssrf
2+
3+
import (
4+
"main/context"
5+
"main/utils"
6+
"testing"
7+
)
8+
9+
func TestCheckResolvedIpForSSRF_NoStoredInterceptorResult_ReturnsNil(t *testing.T) {
10+
context.ResetEventContext()
11+
t.Cleanup(func() { context.ResetEventContext() })
12+
13+
if res := CheckResolvedIpForSSRF("127.0.0.1"); res != nil {
14+
t.Fatalf("expected nil, got %#v", res)
15+
}
16+
}
17+
18+
func TestCheckResolvedIpForSSRF_PublicIp_ReturnsNil(t *testing.T) {
19+
context.ResetEventContext()
20+
t.Cleanup(func() { context.ResetEventContext() })
21+
22+
ir := &utils.InterceptorResult{
23+
Operation: "curl_exec",
24+
Kind: utils.Ssrf,
25+
Source: "body",
26+
Metadata: map[string]string{},
27+
Payload: "http://example.test",
28+
}
29+
context.EventContextSetCurrentSsrfInterceptorResult(ir)
30+
31+
if res := CheckResolvedIpForSSRF("8.8.8.8"); res != nil {
32+
t.Fatalf("expected nil, got %#v", res)
33+
}
34+
if _, ok := ir.Metadata["isPrivateIp"]; ok {
35+
t.Fatalf("expected isPrivateIp to not be set for public IP")
36+
}
37+
if _, ok := ir.Metadata["resolvedIp"]; ok {
38+
t.Fatalf("expected resolvedIp to not be set for public IP")
39+
}
40+
}
41+
42+
func TestCheckResolvedIpForSSRF_PrivateIp_ReturnsInterceptorResultWithMetadata(t *testing.T) {
43+
context.ResetEventContext()
44+
t.Cleanup(func() { context.ResetEventContext() })
45+
46+
ir := &utils.InterceptorResult{
47+
Operation: "curl_exec",
48+
Kind: utils.Ssrf,
49+
Source: "body",
50+
Metadata: map[string]string{},
51+
Payload: "http://example.test",
52+
}
53+
context.EventContextSetCurrentSsrfInterceptorResult(ir)
54+
55+
res := CheckResolvedIpForSSRF("127.0.0.1")
56+
if res == nil {
57+
t.Fatalf("expected non-nil interceptor result")
58+
}
59+
if res != ir {
60+
t.Fatalf("expected returned interceptor result to be the stored one")
61+
}
62+
if got := res.Metadata["resolvedIp"]; got != "127.0.0.1" {
63+
t.Fatalf("expected resolvedIp=127.0.0.1, got %q", got)
64+
}
65+
if got := res.Metadata["isPrivateIp"]; got != "true" {
66+
t.Fatalf("expected isPrivateIp=true, got %q", got)
67+
}
68+
}
69+
70+
func TestCheckEffectiveHostnameForSSRF_PrivateIpHostname_ReturnsInterceptorResultWithMetadata(t *testing.T) {
71+
context.ResetEventContext()
72+
t.Cleanup(func() { context.ResetEventContext() })
73+
74+
ir := &utils.InterceptorResult{
75+
Operation: "curl_exec",
76+
Kind: utils.Ssrf,
77+
Source: "body",
78+
Metadata: map[string]string{},
79+
Payload: "http://example.test",
80+
}
81+
context.EventContextSetCurrentSsrfInterceptorResult(ir)
82+
83+
res := CheckEffectiveHostnameForSSRF("127.0.0.1")
84+
if res == nil {
85+
t.Fatalf("expected non-nil interceptor result")
86+
}
87+
if res != ir {
88+
t.Fatalf("expected returned interceptor result to be the stored one")
89+
}
90+
if got := res.Metadata["effectiveHostname"]; got != "127.0.0.1" {
91+
t.Fatalf("expected effectiveHostname=127.0.0.1, got %q", got)
92+
}
93+
if got := res.Metadata["resolvedIp"]; got != "127.0.0.1" {
94+
t.Fatalf("expected resolvedIp=127.0.0.1, got %q", got)
95+
}
96+
if got := res.Metadata["isPrivateIp"]; got != "true" {
97+
t.Fatalf("expected isPrivateIp=true, got %q", got)
98+
}
99+
}
100+
101+
func TestCheckEffectiveHostnameForSSRF_IMDSHostname_ReturnsInterceptorResultWithIMDSMetadata(t *testing.T) {
102+
context.ResetEventContext()
103+
t.Cleanup(func() { context.ResetEventContext() })
104+
105+
ir := &utils.InterceptorResult{
106+
Operation: "curl_exec",
107+
Kind: utils.Ssrf,
108+
Source: "body",
109+
Metadata: map[string]string{},
110+
Payload: "http://example.test",
111+
}
112+
context.EventContextSetCurrentSsrfInterceptorResult(ir)
113+
114+
res := CheckEffectiveHostnameForSSRF("169.254.169.254")
115+
if res == nil {
116+
t.Fatalf("expected non-nil interceptor result")
117+
}
118+
if res != ir {
119+
t.Fatalf("expected returned interceptor result to be the stored one")
120+
}
121+
if got := res.Metadata["effectiveHostname"]; got != "169.254.169.254" {
122+
t.Fatalf("expected effectiveHostname=169.254.169.254, got %q", got)
123+
}
124+
if got := res.Metadata["resolvedIp"]; got != "169.254.169.254" {
125+
t.Fatalf("expected resolvedIp=169.254.169.254, got %q", got)
126+
}
127+
if got := res.Metadata["isIMDSIp"]; got != "true" {
128+
t.Fatalf("expected isIMDSIp=true, got %q", got)
129+
}
130+
// IMDS IPv4 is also in private ranges in our implementation.
131+
if got := res.Metadata["isPrivateIp"]; got != "true" {
132+
t.Fatalf("expected isPrivateIp=true, got %q", got)
133+
}
134+
}

package/rpm/aikido.spec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Name: aikido-php-firewall
2-
Version: 1.4.12
2+
Version: 1.4.13
33
Release: 1
44
Summary: Aikido PHP Extension
55
License: GPL

0 commit comments

Comments
 (0)