Skip to content

Commit

Permalink
feat(injector: stdlib): instrument os.OpenFile (#219)
Browse files Browse the repository at this point in the history
### What does this PR do?

This PR instrument all file opening via the `os.Openfile` function for
RASP LFI and is the sister PR of
DataDog/dd-trace-go#2781

- [x] Write intrumentation file
- [x] Write integration tests for the `os` package
- [x] Add `tracer-internal: true` to the `dd:orchestrion-enabled`
instrumentation to properly enable the GLS storage in dd-trace-go
- [x] Add more tests for `dd:orchestrion-enabled`    

### Motivation

Support for LFI protection.

### Reviewer's Checklist
<!--
* Authors can use this list as a reference to ensure that there are no
problems
during the review but the signing off is to be done by the reviewer(s).
-->

- [ ] Changed code has unit tests for its functionality.

---------

Signed-off-by: Eliott Bouhana <eliott.bouhana@datadoghq.com>
  • Loading branch information
eliottness authored Aug 29, 2024
1 parent aee181b commit f109ee0
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 6 deletions.
12 changes: 11 additions & 1 deletion .github/workflows/workflow_call.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,22 @@ on:
required: false
type: string
default: main
runs-on:
description: 'The runner to use for the job'
required: false
type: string
default: ubuntu-latest
workflow_call:
inputs:
dd-trace-go-ref:
type: string
required: true
description: 'The ref to checkout dd-trace-go at'
runs-on:
description: 'The runner to use for the job'
required: false
type: string
default: ubuntu-latest

permissions: read-all

Expand All @@ -22,7 +32,7 @@ concurrency:

jobs:
integration-tests:
runs-on: ubuntu-latest-16-cores
runs-on: ${{ inputs.runs-on }}
name: Integration Smoke Tests
steps:
- name: Checkout orchestrion
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
orchestrion
coverage/
venv/
.python-version
116 changes: 116 additions & 0 deletions _integration-tests/tests/os/lfi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2023-present Datadog, Inc.

//go:build integration

package os

import (
"context"
"fmt"
"net/http"
"os"
"runtime"
"testing"
"time"

"orchestrion/integration/validator/trace"

"github.com/stretchr/testify/require"

"gopkg.in/DataDog/dd-trace-go.v1/appsec/events"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

type TestCase struct {
*http.Server
*testing.T
}

func (tc *TestCase) Setup(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("appsec does not support Windows")
}

t.Setenv("DD_APPSEC_RULES", "./testdata/rasp-only-rules.json")
t.Setenv("DD_APPSEC_ENABLED", "true")
t.Setenv("DD_APPSEC_RASP_ENABLED", "true")
t.Setenv("DD_APPSEC_WAF_TIMEOUT", "1h")
mux := http.NewServeMux()
tc.Server = &http.Server{
Addr: "127.0.0.1:8080",
Handler: mux,
}

mux.HandleFunc("/", tc.handleRoot)

go func() { require.ErrorIs(t, tc.Server.ListenAndServe(), http.ErrServerClosed) }()
}

func (tc *TestCase) Run(t *testing.T) {
tc.T = t
resp, err := http.Get(fmt.Sprintf("http://%s/?path=/etc/passwd", tc.Server.Addr))
require.NoError(t, err)
require.Equal(t, http.StatusForbidden, resp.StatusCode)
}

func (tc *TestCase) Teardown(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

require.NoError(t, tc.Server.Shutdown(ctx))
}

func (tc *TestCase) ExpectedTraces() trace.Spans {
return trace.Spans{
{
Tags: map[string]any{
"name": "http.request",
"resource": "GET /",
"type": "http",
},
Meta: map[string]any{
"component": "net/http",
"span.kind": "client",
},
Children: trace.Spans{
{
Tags: map[string]any{
"name": "http.request",
"resource": "GET /",
"type": "web",
},
Meta: map[string]any{
"component": "net/http",
"span.kind": "server",
"appsec.blocked": "true",
"is.security.error": "true",
},
},
},
},
}
}

func (tc *TestCase) handleRoot(w http.ResponseWriter, _ *http.Request) {

fp, err := os.Open("/etc/passwd")

require.ErrorIs(tc.T, err, &events.BlockingSecurityEvent{})
if events.IsSecurityError(err) { // TODO: response writer instrumentation to not have to do that
span, _ := tracer.SpanFromContext(context.TODO())
span.SetTag("is.security.error", true)
return
}

if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

defer fp.Close()

w.WriteHeader(http.StatusOK)
}
2 changes: 2 additions & 0 deletions _integration-tests/tests/suite.generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

158 changes: 158 additions & 0 deletions _integration-tests/tests/testdata/rasp-only-rules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
{
"version": "2.2",
"metadata": {
"rules_version": "1.4.2"
},
"rules": [
{
"id": "rasp-930-100",
"name": "Local file inclusion exploit",
"tags": {
"type": "lfi",
"category": "vulnerability_trigger",
"cwe": "22",
"capec": "1000/255/153/126",
"confidence": "0",
"module": "rasp"
},
"conditions": [
{
"parameters": {
"resource": [
{
"address": "server.io.fs.file"
}
],
"params": [
{
"address": "server.request.query"
},
{
"address": "server.request.body"
},
{
"address": "server.request.path_params"
},
{
"address": "grpc.server.request.message"
},
{
"address": "graphql.server.all_resolvers"
},
{
"address": "graphql.server.resolver"
}
]
},
"operator": "lfi_detector"
}
],
"transformers": [],
"on_match": [
"stack_trace",
"block"
]
},
{
"id": "rasp-934-100",
"name": "Server-side request forgery exploit",
"tags": {
"type": "ssrf",
"category": "vulnerability_trigger",
"cwe": "918",
"capec": "1000/225/115/664",
"confidence": "0",
"module": "rasp"
},
"conditions": [
{
"parameters": {
"resource": [
{
"address": "server.io.net.url"
}
],
"params": [
{
"address": "server.request.query"
},
{
"address": "server.request.body"
},
{
"address": "server.request.path_params"
},
{
"address": "grpc.server.request.message"
},
{
"address": "graphql.server.all_resolvers"
},
{
"address": "graphql.server.resolver"
}
]
},
"operator": "ssrf_detector"
}
],
"transformers": [],
"on_match": [
"stack_trace",
"block"
]
},
{
"id": "rasp-942-100",
"name": "SQL injection exploit",
"tags": {
"type": "sql_injection",
"category": "vulnerability_trigger",
"cwe": "89",
"capec": "1000/152/248/66",
"confidence": "0",
"module": "rasp"
},
"conditions": [
{
"parameters": {
"resource": [
{
"address": "server.db.statement"
}
],
"params": [
{
"address": "server.request.query"
},
{
"address": "server.request.body"
},
{
"address": "server.request.path_params"
},
{
"address": "graphql.server.all_resolvers"
},
{
"address": "graphql.server.resolver"
}
],
"db_type": [
{
"address": "server.db.system"
}
]
},
"operator": "sqli_detector"
}
],
"transformers": [],
"on_match": [
"stack_trace",
"block"
]
}
],
"rules_data": []
}
2 changes: 2 additions & 0 deletions _integration-tests/utils/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/google/uuid"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)

Expand Down Expand Up @@ -128,6 +129,7 @@ func (a *MockAgent) NewSession(t *testing.T) (session *Session, err error) {
}

t.Logf("Started test session with ID %s\n", session.token.String())

tracer.Start(
tracer.WithAgentAddr(fmt.Sprintf("127.0.0.1:%d", a.port)),
tracer.WithSampler(tracer.NewAllSampler()),
Expand Down
5 changes: 2 additions & 3 deletions _integration-tests/utils/agent/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
ddapm-test-agent==1.17.0
ddapm-test-agent==1.18.0
## The following requirements were added by pip freeze:
aiohappyeyeballs==2.4.0
aiohttp==3.10.5
aiosignal==1.3.1
async-timeout==4.0.3
attrs==24.2.0
certifi==2024.7.4
charset-normalizer==3.3.2
Expand All @@ -12,7 +11,7 @@ frozenlist==1.4.1
idna==3.8
msgpack==1.0.8
multidict==6.0.5
protobuf==5.27.3
protobuf==5.28.0
requests==2.32.3
six==1.16.0
typing_extensions==4.12.2
Expand Down
2 changes: 1 addition & 1 deletion _integration-tests/utils/generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func main() {

file.Var().Id("suite").Op("=").Map(jen.String()).Id("testCase").ValuesFunc(func(g *jen.Group) {
for _, entry := range entries {
if !entry.IsDir() {
if !entry.IsDir() || entry.Name() == "testdata" {
continue
}

Expand Down
Loading

0 comments on commit f109ee0

Please sign in to comment.