mirror of
https://github.com/trufflesecurity/trufflehog.git
synced 2024-11-10 07:04:24 +00:00
Use bad json in slackwebhooks (#2193)
* add rotation guides to SlackWebhook tests * begin cleaning up tests * have slack webhook detector use malformed json * update test secrets --------- Co-authored-by: Ahrav Dutta <ahrav.dutta@trufflesec.com>
This commit is contained in:
parent
61c7d52a43
commit
405f356071
2 changed files with 69 additions and 84 deletions
|
@ -60,7 +60,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||||
client = defaultClient
|
client = defaultClient
|
||||||
}
|
}
|
||||||
|
|
||||||
payload := strings.NewReader(`{"text": ""}`)
|
// We don't want to actually send anything to webhooks we find. To verify them without spamming them, we
|
||||||
|
// send an intentionally malformed message and look for a particular expected error message.
|
||||||
|
payload := strings.NewReader(`intentionally malformed JSON from Trufflehog scan`)
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", resMatch, payload)
|
req, err := http.NewRequestWithContext(ctx, "POST", resMatch, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -76,12 +78,18 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||||
|
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
|
||||||
if res.StatusCode >= 200 && res.StatusCode < 300 || (res.StatusCode == 400 && (bytes.Equal(bodyBytes, []byte("no_text")) || bytes.Equal(bodyBytes, []byte("missing_text")))) {
|
switch {
|
||||||
|
case res.StatusCode >= http.StatusOK && res.StatusCode < http.StatusMultipleChoices:
|
||||||
|
// Hopefully this never happens - it means we actually sent something to a channel somewhere. But
|
||||||
|
// we at least know the secret is verified.
|
||||||
s1.Verified = true
|
s1.Verified = true
|
||||||
} else if res.StatusCode == 401 || res.StatusCode == 403 {
|
case res.StatusCode == http.StatusBadRequest && bytes.Equal(bodyBytes, []byte("invalid_payload")):
|
||||||
// The secret is determinately not verified (nothing to do)
|
s1.Verified = true
|
||||||
} else {
|
case res.StatusCode == http.StatusNotFound:
|
||||||
err = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
|
// Not a real webhook or the owning app's OAuth token has been revoked or the app has been deleted
|
||||||
|
// You might want to handle this case or log it.
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unexpected HTTP response status %d: %s", res.StatusCode, bodyBytes)
|
||||||
s1.SetVerificationError(err, resMatch)
|
s1.SetVerificationError(err, resMatch)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -20,12 +20,14 @@ import (
|
||||||
func TestSlackWebhook_FromChunk(t *testing.T) {
|
func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
|
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||||
}
|
}
|
||||||
secret := testSecrets.MustGetField("SLACKWEBHOOK_TOKEN")
|
secret := testSecrets.MustGetField("SLACKWEBHOOK_TOKEN")
|
||||||
inactiveSecret := testSecrets.MustGetField("SLACKWEBHOOK_INACTIVE")
|
inactiveSecret := testSecrets.MustGetField("SLACKWEBHOOK_INACTIVE")
|
||||||
|
deletedWebhook := testSecrets.MustGetField("SLACKWEBHOOK_DELETED")
|
||||||
|
deletedUserActiveWebhook := testSecrets.MustGetField("SLACKWEBHOOK_DELETED_USER_ACTIVE_WEBHOOK")
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
@ -41,7 +43,7 @@ func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
wantVerificationErr bool
|
wantVerificationErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "found, verified",
|
name: "active webhook",
|
||||||
s: Scanner{},
|
s: Scanner{},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
@ -51,6 +53,7 @@ func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
want: []detectors.Result{
|
want: []detectors.Result{
|
||||||
{
|
{
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
||||||
|
ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
|
||||||
Verified: true,
|
Verified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -58,50 +61,35 @@ func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
wantVerificationErr: false,
|
wantVerificationErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "found, verified, 400 no_text",
|
name: "active webhook created by a deactivated user",
|
||||||
s: Scanner{client: common.ConstantResponseHttpClient(400, "no_text")},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
|
||||||
Verified: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, verified, 400 missing_text",
|
|
||||||
s: Scanner{client: common.ConstantResponseHttpClient(400, "missing_text")},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
|
||||||
Verified: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, unverified",
|
|
||||||
s: Scanner{},
|
s: Scanner{},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
|
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", deletedUserActiveWebhook)),
|
||||||
verify: true,
|
verify: true,
|
||||||
},
|
},
|
||||||
want: []detectors.Result{
|
want: []detectors.Result{
|
||||||
{
|
{
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
||||||
|
ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
|
||||||
|
Verified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
wantVerificationErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deleted webhook",
|
||||||
|
s: Scanner{},
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", deletedWebhook)),
|
||||||
|
verify: true,
|
||||||
|
},
|
||||||
|
want: []detectors.Result{
|
||||||
|
{
|
||||||
|
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
||||||
|
ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
|
||||||
Verified: false,
|
Verified: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -109,19 +97,43 @@ func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
wantVerificationErr: false,
|
wantVerificationErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not found",
|
name: "webhook from app with revoked token",
|
||||||
s: Scanner{},
|
s: Scanner{},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
data: []byte("You cannot find the secret within"),
|
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", inactiveSecret)),
|
||||||
verify: true,
|
verify: true,
|
||||||
},
|
},
|
||||||
want: nil,
|
want: []detectors.Result{
|
||||||
|
{
|
||||||
|
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
||||||
|
ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
|
||||||
|
Verified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
wantVerificationErr: false,
|
wantVerificationErr: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "found, would be verified if not for timeout",
|
name: "unexpected webhook response",
|
||||||
|
s: Scanner{client: common.ConstantResponseHttpClient(500, "oh no")},
|
||||||
|
args: args{
|
||||||
|
ctx: context.Background(),
|
||||||
|
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
|
||||||
|
verify: true,
|
||||||
|
},
|
||||||
|
want: []detectors.Result{
|
||||||
|
{
|
||||||
|
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
||||||
|
ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
|
||||||
|
Verified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
wantVerificationErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout",
|
||||||
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
|
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
@ -131,40 +143,7 @@ func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
want: []detectors.Result{
|
want: []detectors.Result{
|
||||||
{
|
{
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
||||||
Verified: false,
|
ExtraData: map[string]string{"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/"},
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, verified but unexpected api surface",
|
|
||||||
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
|
||||||
Verified: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
wantVerificationErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "found, account disabled",
|
|
||||||
s: Scanner{client: common.ConstantResponseHttpClient(400, disabledAccountResponse)},
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
data: []byte(fmt.Sprintf("You can find a slackwebhook secret %s within", secret)),
|
|
||||||
verify: true,
|
|
||||||
},
|
|
||||||
want: []detectors.Result{
|
|
||||||
{
|
|
||||||
DetectorType: detectorspb.DetectorType_SlackWebhook,
|
|
||||||
Verified: false,
|
Verified: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -195,8 +174,6 @@ func TestSlackWebhook_FromChunk(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const disabledAccountResponse = `missing_text_or_fallback_or_attachments`
|
|
||||||
|
|
||||||
func BenchmarkFromData(benchmark *testing.B) {
|
func BenchmarkFromData(benchmark *testing.B) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
s := Scanner{}
|
s := Scanner{}
|
||||||
|
|
Loading…
Reference in a new issue