mirror of
https://github.com/projectdiscovery/nuclei.git
synced 2026-11-09 12:21:25 -10:00
127 lines
4.8 KiB
Go
127 lines
4.8 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/model/types/severity"
|
|
"github.com/projectdiscovery/nuclei/v3/pkg/output"
|
|
"github.com/projectdiscovery/retryablehttp-go"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
|
|
|
func testEvent() *output.ResultEvent {
|
|
return &output.ResultEvent{
|
|
TemplateID: "test-template",
|
|
Host: "https://target.example",
|
|
Info: model.Info{Name: "test", SeverityHolder: severity.Holder{Severity: severity.High}},
|
|
}
|
|
}
|
|
|
|
func clientWithTransport(rt http.RoundTripper) *retryablehttp.Client {
|
|
c := retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)
|
|
c.HTTPClient = &http.Client{Transport: rt}
|
|
return c
|
|
}
|
|
|
|
// The tracker must send every request through the caller's client, so a
|
|
// caller's address policy also covers the GitHub API base URL.
|
|
func TestNewUsesCallerHTTPClient(t *testing.T) {
|
|
var serverHits atomic.Int32
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
serverHits.Add(1)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"number":1,"html_url":"https://github.example/o/r/issues/1"}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
t.Run("requests go through the caller's transport", func(t *testing.T) {
|
|
var seen atomic.Int32
|
|
rt := roundTripFunc(func(r *http.Request) (*http.Response, error) {
|
|
seen.Add(1)
|
|
require.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
|
|
return http.DefaultTransport.RoundTrip(r)
|
|
})
|
|
tracker, err := New(&Options{BaseURL: srv.URL + "/", Username: "u", Owner: "o", ProjectName: "r", Token: "test-token", HttpClient: clientWithTransport(rt)})
|
|
require.NoError(t, err)
|
|
_, err = tracker.CreateIssue(testEvent())
|
|
require.NoError(t, err)
|
|
require.Positive(t, seen.Load())
|
|
})
|
|
|
|
t.Run("a refusing transport stops the request", func(t *testing.T) {
|
|
before := serverHits.Load()
|
|
denied := errors.New("denied by address policy")
|
|
rt := roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, denied })
|
|
tracker, err := New(&Options{BaseURL: srv.URL + "/", Username: "u", Owner: "o", ProjectName: "r", Token: "test-token", HttpClient: clientWithTransport(rt)})
|
|
require.NoError(t, err)
|
|
_, err = tracker.CreateIssue(testEvent())
|
|
require.ErrorContains(t, err, "denied by address policy")
|
|
require.Equal(t, before, serverHits.Load(), "the request reached the server")
|
|
})
|
|
}
|
|
|
|
func TestNewRefusesUntrustedCertificateFromInsecureCallerClient(t *testing.T) {
|
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"number":1}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
insecure := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
|
|
tracker, err := New(&Options{BaseURL: srv.URL + "/", Username: "u", Owner: "o", ProjectName: "r", Token: "test-token", HttpClient: clientWithTransport(insecure)})
|
|
require.NoError(t, err)
|
|
_, err = tracker.CreateIssue(testEvent())
|
|
require.ErrorContains(t, err, "certificate")
|
|
}
|
|
|
|
type wrappingTransport struct{ base http.RoundTripper }
|
|
|
|
func (w *wrappingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
|
return w.base.RoundTrip(r)
|
|
}
|
|
func (w *wrappingTransport) Unwrap() http.RoundTripper { return w.base }
|
|
|
|
// The scan client wraps its transport and, with a SOCKS proxy, does TLS in its
|
|
// own dialer with the scan TLS config, which skips verification.
|
|
func TestNewRefusesUntrustedCertificateFromInsecureTLSDialer(t *testing.T) {
|
|
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusCreated)
|
|
_, _ = w.Write([]byte(`{"number":1}`))
|
|
}))
|
|
defer srv.Close()
|
|
|
|
var dialed atomic.Int32
|
|
var dialer net.Dialer
|
|
base := &http.Transport{
|
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
dialed.Add(1)
|
|
return dialer.DialContext(ctx, network, addr)
|
|
},
|
|
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
conn, err := dialer.DialContext(ctx, network, addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return tls.Client(conn, &tls.Config{InsecureSkipVerify: true}), nil
|
|
},
|
|
}
|
|
tracker, err := New(&Options{BaseURL: srv.URL + "/", Username: "u", Owner: "o", ProjectName: "r", Token: "test-token", HttpClient: clientWithTransport(&wrappingTransport{base: base})})
|
|
require.NoError(t, err)
|
|
_, err = tracker.CreateIssue(testEvent())
|
|
require.ErrorContains(t, err, "certificate")
|
|
require.Positive(t, dialed.Load(), "the caller's dialer was not used")
|
|
}
|