))
diff --git a/go/grpc/security/grpc-server-insecure-connection.go b/go/grpc/security/grpc-server-insecure-connection.go
new file mode 100644
index 00000000..aeb45aed
--- /dev/null
+++ b/go/grpc/security/grpc-server-insecure-connection.go
@@ -0,0 +1,89 @@
+package insecuregrpc
+
+import (
+ "crypto/x509"
+ "log"
+ "net/http"
+ "net/http/httptest"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+)
+
+// cf. https://blog.gopheracademy.com/advent-2019/go-grps-and-tls/#connection-without-encryption
+func unsafe() {
+ // Server
+ // ruleid:grpc-server-insecure-connection
+ s := grpc.NewServer()
+ // ... register gRPC services ...
+ if err = s.Serve(lis); err != nil {
+ log.Fatalf("failed to serve: %v", err)
+ }
+}
+
+func safe() {
+ // Server
+ // ok:grpc-server-insecure-connection
+ s := grpc.NewServer(grpc.Creds(credentials.NewClientTLSFromCert(x509.NewCertPool(), "")))
+ // ... register gRPC services ...
+ if err = s.Serve(lis); err != nil {
+ log.Fatalf("failed to serve: %v", err)
+ }
+}
+
+// False Positive test
+// cf. https://github.com/daghan/invoicer-chapter2/blob/4c5b00408a4aeece86d98ad3ef1c88e610053dfc/vendor/golang.org/x/net/websocket/websocket_test.go#L129
+func startServer() {
+ http.Handle("/echo", Handler(echoServer))
+ http.Handle("/count", Handler(countServer))
+ http.Handle("/ctrldata", Handler(ctrlAndDataServer))
+ subproto := Server{
+ Handshake: subProtocolHandshake,
+ Handler: Handler(subProtoServer),
+ }
+ http.Handle("/subproto", subproto)
+ // ok:grpc-server-insecure-connection
+ server := httptest.NewServer(nil)
+ serverAddr = server.Listener.Addr().String()
+ log.Print("Test WebSocket server listening on ", serverAddr)
+}
+
+// False Positive test - options have grpc.Creds
+func startServerWithOpts() {
+ options := []grpc.ServerOption{
+ grpc.Creds(credentials.NewClientTLSFromCert(pool, addr)),
+ }
+ // ok:grpc-server-insecure-connection
+ grpcServer := grpc.NewServer(options...)
+ _ = grpcServer
+}
+
+// False Positive test - options have grpc.Creds, credentials in a variable
+func startServerCredsVar() {
+ creds := credentials.NewClientTLSFromCert(xpool, xaddr)
+ options := []grpc.ServerOption{
+ grpc.Creds(creds),
+ grpc.UnaryInterceptor(auth.GRPCInterceptor),
+ }
+ // ok:grpc-server-insecure-connection
+ grpcServer := grpc.NewServer(options...)
+ _ = grpcServer
+}
+
+func startServerWithOtherCreds() {
+ creds := credentials.NewTLS(tlsConfig)
+ logger := penglog.GlobalLogger()
+ logInterceptor := penggrpc.NewAccessLogInterceptor(&logger, grpcLogFields)
+ opts := []grpc.ServerOption{
+ grpc.Creds(creds),
+ grpc.ChainUnaryInterceptor(
+ logInterceptor.UnaryServerInterceptor,
+ auth.GRPCInterceptor,
+ ),
+ grpc.MaxRecvMsgSize(maxRecvMsgSize),
+ }
+ // ok:grpc-server-insecure-connection
+ grpcServer := grpc.NewServer(opts)
+ _ = grpcServer
+}
+
diff --git a/go/grpc/security/grpc-server-insecure-connection.yaml b/go/grpc/security/grpc-server-insecure-connection.yaml
new file mode 100644
index 00000000..c5d6c3e7
--- /dev/null
+++ b/go/grpc/security/grpc-server-insecure-connection.yaml
@@ -0,0 +1,43 @@
+rules:
+- id: grpc-server-insecure-connection
+ metadata:
+ cwe:
+ - 'CWE-300: Channel Accessible by Non-Endpoint'
+ references:
+ - https://blog.gopheracademy.com/advent-2019/go-grps-and-tls/#connection-without-encryption
+ category: security
+ technology:
+ - grpc
+ confidence: HIGH
+ owasp:
+ - A07:2021 - Identification and Authentication Failures
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ message: >-
+ Found an insecure gRPC server without 'grpc.Creds()' or options with credentials.
+ This allows for a connection without
+ encryption to this server.
+ A malicious attacker could tamper with the gRPC message, which could compromise
+ the machine. Include credentials derived
+ from an SSL certificate in order to create a secure gRPC connection. You can create
+ credentials using 'credentials.NewServerTLSFromFile("cert.pem",
+ "cert.key")'.
+ languages:
+ - go
+ severity: ERROR
+ mode: taint
+ pattern-sinks:
+ - requires: OPTIONS and not CREDS
+ pattern: grpc.NewServer($OPT, ...)
+ - requires: EMPTY_CONSTRUCTOR
+ pattern: grpc.NewServer()
+ pattern-sources:
+ - label: OPTIONS
+ pattern: grpc.ServerOption{ ... }
+ - label: CREDS
+ pattern: grpc.Creds(...)
+ - label: EMPTY_CONSTRUCTOR
+ pattern: grpc.NewServer()
+
diff --git a/go/jwt-go/security/audit/jwt-parse-unverified.go b/go/jwt-go/security/audit/jwt-parse-unverified.go
new file mode 100644
index 00000000..ff68b39a
--- /dev/null
+++ b/go/jwt-go/security/audit/jwt-parse-unverified.go
@@ -0,0 +1,37 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/dgrijalva/jwt-go"
+)
+
+func bad1(tokenString string) {
+ // ruleid: jwt-go-parse-unverified
+ token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{})
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ if claims, ok := token.Claims.(jwt.MapClaims); ok {
+ fmt.Println(claims["foo"], claims["exp"])
+ } else {
+ fmt.Println(err)
+ }
+}
+
+func ok1(tokenString string, keyFunc Keyfunc) {
+ // ok: jwt-go-parse-unverified
+ token, err := new(jwt.Parser).ParseWithClaims(tokenString, jwt.MapClaims{}, keyFunc)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ if claims, ok := token.Claims.(jwt.MapClaims); ok {
+ fmt.Println(claims["foo"], claims["exp"])
+ } else {
+ fmt.Println(err)
+ }
+}
diff --git a/go/jwt-go/security/audit/jwt-parse-unverified.yaml b/go/jwt-go/security/audit/jwt-parse-unverified.yaml
new file mode 100644
index 00000000..5916496e
--- /dev/null
+++ b/go/jwt-go/security/audit/jwt-parse-unverified.yaml
@@ -0,0 +1,32 @@
+rules:
+- id: jwt-go-parse-unverified
+ message: >-
+ Detected the decoding of a JWT token without a verify step.
+ Don't use `ParseUnverified` unless you know what you're doing
+ This method parses the token but doesn't validate the signature. It's only ever useful in cases where
+ you know the signature is valid (because it has been checked previously in the stack) and you want
+ to extract values from it.
+ metadata:
+ cwe:
+ - 'CWE-345: Insufficient Verification of Data Authenticity'
+ owasp:
+ - A08:2021 - Software and Data Integrity Failures
+ source-rule-url: https://semgrep.dev/blog/2020/hardcoded-secrets-unverified-tokens-and-other-common-jwt-mistakes/
+ category: security
+ technology:
+ - jwt
+ confidence: MEDIUM
+ references:
+ - https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-inside: |
+ import "github.com/dgrijalva/jwt-go"
+ ...
+ - pattern: |
+ $JWT.ParseUnverified(...)
diff --git a/go/jwt-go/security/jwt-none-alg.go b/go/jwt-go/security/jwt-none-alg.go
new file mode 100644
index 00000000..2da109b3
--- /dev/null
+++ b/go/jwt-go/security/jwt-none-alg.go
@@ -0,0 +1,31 @@
+package main
+
+import (
+ "fmt"
+ "github.com/dgrijalva/jwt-go"
+)
+
+func bad1() {
+ claims := jwt.StandardClaims{
+ ExpiresAt: 15000,
+ Issuer: "test",
+ }
+
+ // ruleid: jwt-go-none-algorithm
+ token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
+ // ruleid: jwt-go-none-algorithm
+ ss, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
+ fmt.Printf("%v %v\n", ss, err)
+}
+
+func ok1(key []byte) {
+ claims := jwt.StandardClaims{
+ ExpiresAt: 15000,
+ Issuer: "test",
+ }
+
+ // ok: jwt-go-none-algorithm
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ ss, err := token.SignedString(key)
+ fmt.Printf("%v %v\n", ss, err)
+}
diff --git a/go/jwt-go/security/jwt-none-alg.yaml b/go/jwt-go/security/jwt-none-alg.yaml
new file mode 100644
index 00000000..f99e91dd
--- /dev/null
+++ b/go/jwt-go/security/jwt-none-alg.yaml
@@ -0,0 +1,39 @@
+rules:
+- id: jwt-go-none-algorithm
+ message: >-
+ Detected use of the 'none' algorithm in a JWT token.
+ The 'none' algorithm assumes the integrity of the token has already
+ been verified. This would allow a malicious actor to forge a JWT token
+ that will automatically be verified. Do not explicitly use the 'none'
+ algorithm. Instead, use an algorithm such as 'HS256'.
+ metadata:
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ source-rule-url: https://semgrep.dev/blog/2020/hardcoded-secrets-unverified-tokens-and-other-common-jwt-mistakes/
+ category: security
+ technology:
+ - jwt
+ confidence: HIGH
+ references:
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ languages: [go]
+ severity: ERROR
+ patterns:
+ - pattern-either:
+ - pattern-inside: |
+ import "github.com/golang-jwt/jwt"
+ ...
+ - pattern-inside: |
+ import "github.com/dgrijalva/jwt-go"
+ ...
+ - pattern-either:
+ - pattern: |
+ jwt.SigningMethodNone
+ - pattern: jwt.UnsafeAllowNoneSignatureType
diff --git a/go/jwt-go/security/jwt.go b/go/jwt-go/security/jwt.go
new file mode 100644
index 00000000..3959feba
--- /dev/null
+++ b/go/jwt-go/security/jwt.go
@@ -0,0 +1,96 @@
+// https://www.sohamkamani.com/blog/golang/2019-01-01-jwt-authentication/
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/dgrijalva/jwt-go"
+)
+
+//...
+// import the jwt-go library
+
+//...
+
+var users = map[string]string{
+ "user1": "password1",
+ "user2": "password2",
+}
+
+// Create a struct to read the username and password from the request body
+type Credentials struct {
+ Password string `json:"password"`
+ Username string `json:"username"`
+}
+
+// Create a struct that will be encoded to a JWT.
+// We add jwt.StandardClaims as an embedded type, to provide fields like expiry time
+type Claims struct {
+ Username string `json:"username"`
+ jwt.StandardClaims
+}
+
+// Create the Signin handler
+func Signin(w http.ResponseWriter, r *http.Request) {
+
+ // Create the JWT key used to create the signature
+ var jwtKey = []byte("my_secret_key")
+ var x = "foo"
+
+ var creds Credentials
+ // Get the JSON body and decode into credentials
+ err := json.NewDecoder(r.Body).Decode(&creds)
+ if err != nil {
+ // If the structure of the body is wrong, return an HTTP error
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ // Get the expected password from our in memory map
+ expectedPassword, ok := users[creds.Username]
+
+ // If a password exists for the given user
+ // AND, if it is the same as the password we received, the we can move ahead
+ // if NOT, then we return an "Unauthorized" status
+ if !ok || expectedPassword != creds.Password {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ // Declare the expiration time of the token
+ // here, we have kept it as 5 minutes
+ expirationTime := time.Now().Add(5 * time.Minute)
+ // Create the JWT claims, which includes the username and expiry time
+ claims := &Claims{
+ Username: creds.Username,
+ StandardClaims: jwt.StandardClaims{
+ // In JWT, the expiry time is expressed as unix milliseconds
+ ExpiresAt: expirationTime.Unix(),
+ },
+ }
+
+ // Declare the token with the algorithm used for signing, and the claims
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ // ruleid: hardcoded-jwt-key
+ tokenString, err := token.SignedString(jwtKey)
+ // ruleid: hardcoded-jwt-key
+ tokenString, err := token.SignedString([]byte("my_secret_key"))
+ if err != nil {
+ // If there is an error in creating the JWT return an internal server error
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ // Finally, we set the client cookie for "token" as the JWT we just generated
+ // we also set an expiry time which is the same as the token itself
+ http.SetCookie(w, &http.Cookie{
+ Name: "token",
+ Value: tokenString,
+ Expires: expirationTime,
+ })
+
+
+}
diff --git a/go/jwt-go/security/jwt.yaml b/go/jwt-go/security/jwt.yaml
new file mode 100644
index 00000000..261316b7
--- /dev/null
+++ b/go/jwt-go/security/jwt.yaml
@@ -0,0 +1,42 @@
+rules:
+- id: hardcoded-jwt-key
+ message: >-
+ A hard-coded credential was detected. It is not recommended to store credentials in source-code,
+ as this risks secrets
+ being leaked and used by either an internal or external malicious adversary. It is recommended to
+ use environment variables to securely provide credentials or retrieve credentials from a secure
+ vault or HSM (Hardware Security Module).
+ options:
+ interfile: true
+ metadata:
+ cwe:
+ - 'CWE-798: Use of Hard-coded Credentials'
+ references:
+ - https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
+ owasp:
+ - A07:2021 - Identification and Authentication Failures
+ category: security
+ technology:
+ - jwt
+ - secrets
+ confidence: MEDIUM
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: HIGH
+ impact: MEDIUM
+ interfile: true
+ severity: WARNING
+ languages: [go]
+ mode: taint
+ pattern-sources:
+ - patterns:
+ - pattern-inside: |
+ []byte("$F")
+ pattern-sinks:
+ - patterns:
+ - pattern-either:
+ - pattern-inside: |
+ $TOKEN.SignedString($F)
+ - focus-metavariable: $F
diff --git a/go/lang/best-practice/channel-guarded-with-mutex.go b/go/lang/best-practice/channel-guarded-with-mutex.go
new file mode 100644
index 00000000..2eefa857
--- /dev/null
+++ b/go/lang/best-practice/channel-guarded-with-mutex.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+func ReadMessage() {
+ messages := make(chan string)
+
+ go func() {
+ messages <- "ping"
+ }()
+
+ // ok: channel-guarded-with-mutex
+ msg := <-messages
+ fmt.Println(msg)
+}
+
+func ReadMessageMutex() {
+ var mutex = &sync.Mutex{}
+ messages := make(chan string)
+
+ go func() {
+ messages <- "ping"
+ }()
+
+ // ruleid: channel-guarded-with-mutex
+ mutex.Lock()
+ msg := <-messages
+ mutex.Unlock()
+ fmt.Println(msg)
+}
diff --git a/go/lang/best-practice/channel-guarded-with-mutex.yaml b/go/lang/best-practice/channel-guarded-with-mutex.yaml
new file mode 100644
index 00000000..ab610954
--- /dev/null
+++ b/go/lang/best-practice/channel-guarded-with-mutex.yaml
@@ -0,0 +1,22 @@
+rules:
+ - id: channel-guarded-with-mutex
+ pattern-either:
+ - pattern: |
+ $MUX.Lock()
+ $VALUE <- $CHANNEL
+ $MUX.Unlock()
+ - pattern: |
+ $MUX.Lock()
+ $VALUE = <- $CHANNEL
+ $MUX.Unlock()
+ message: >-
+ Detected a channel guarded with a mutex. Channels already have
+ an internal mutex, so this is unnecessary. Remove the mutex.
+ See https://hackmongo.com/page/golang-antipatterns/#guarded-channel
+ for more information.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ category: best-practice
+ technology:
+ - go
diff --git a/go/lang/best-practice/hidden-goroutine.go b/go/lang/best-practice/hidden-goroutine.go
new file mode 100644
index 00000000..9019c9d0
--- /dev/null
+++ b/go/lang/best-practice/hidden-goroutine.go
@@ -0,0 +1,26 @@
+package main
+
+import "fmt"
+
+// ruleid: hidden-goroutine
+func HiddenGoroutine() {
+ go func() {
+ fmt.Println("hello world")
+ }()
+}
+
+// ok: hidden-goroutine
+func FunctionThatCallsGoroutineIsOk() {
+ fmt.Println("This is normal")
+ go func() {
+ fmt.Println("This is OK because the function does other things")
+ }()
+}
+
+// ok: hidden-goroutine
+func FunctionThatCallsGoroutineAlsoOk() {
+ go func() {
+ fmt.Println("This is OK because the function does other things")
+ }()
+ fmt.Println("This is normal")
+}
diff --git a/go/lang/best-practice/hidden-goroutine.yaml b/go/lang/best-practice/hidden-goroutine.yaml
new file mode 100644
index 00000000..16425cca
--- /dev/null
+++ b/go/lang/best-practice/hidden-goroutine.yaml
@@ -0,0 +1,27 @@
+rules:
+ - id: hidden-goroutine
+ patterns:
+ - pattern-not: |
+ func $FUNC(...) {
+ go func() {
+ ...
+ }(...)
+ $MORE
+ }
+ - pattern: |
+ func $FUNC(...) {
+ go func() {
+ ...
+ }(...)
+ }
+ message: >-
+ Detected a hidden goroutine. Function invocations are expected to synchronous,
+ and this function will execute asynchronously because all it does is call a
+ goroutine. Instead, remove the internal goroutine and call the function using
+ 'go'.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ category: best-practice
+ technology:
+ - go
diff --git a/go/lang/correctness/dos/zip_bomb.go b/go/lang/correctness/dos/zip_bomb.go
new file mode 100644
index 00000000..86bdcd12
--- /dev/null
+++ b/go/lang/correctness/dos/zip_bomb.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+ "archive/zip"
+ "io"
+ "os"
+ "strconv"
+)
+
+func main() {
+ // ruleid: potential-dos-via-decompression-bomb
+ r, err := zip.OpenReader("tmp.zip")
+ if err != nil {
+ panic(err)
+ }
+ defer r.Close()
+
+ for i, f := range r.File {
+ out, err := os.OpenFile("output"+strconv.Itoa(i), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ if err != nil {
+ panic(err)
+ }
+
+ rc, err := f.Open()
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = io.Copy(out, rc)
+
+ out.Close()
+ rc.Close()
+
+ if err != nil {
+ panic(err)
+ }
+ }
+}
diff --git a/go/lang/correctness/dos/zlib_bomb.go b/go/lang/correctness/dos/zlib_bomb.go
new file mode 100644
index 00000000..8d5b612d
--- /dev/null
+++ b/go/lang/correctness/dos/zlib_bomb.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "bytes"
+ "compress/zlib"
+ "io"
+ "os"
+)
+
+func main() {
+ buff := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207,
+ 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
+ b := bytes.NewReader(buff)
+ // ruleid: potential-dos-via-decompression-bomb
+ r, err := zlib.NewReader(b)
+ if err != nil {
+ panic(err)
+ }
+ io.Copy(os.Stdout, r)
+
+ r.Close()
+}
diff --git a/go/lang/correctness/looppointer.go b/go/lang/correctness/looppointer.go
new file mode 100644
index 00000000..5d165d50
--- /dev/null
+++ b/go/lang/correctness/looppointer.go
@@ -0,0 +1,39 @@
+func() {
+ values := []string{"a", "b", "c"}
+ var funcs []func()
+ // ruleid:exported_loop_pointer
+ for _, val := range values {
+ funcs = append(funcs, func() {
+ fmt.Println(&val)
+ })
+ }
+}
+
+func() {
+ // ruleid:exported_loop_pointer
+ for _, val := range values {
+ print_pointer(&val)
+ }
+}
+
+
+func() {
+ values := []string{"a", "b", "c"}
+ var funcs []func()
+ // ok:exported_loop_pointer
+ for _, val := range values {
+ val := val // pin!
+ funcs = append(funcs, func() {
+ fmt.Println(&val)
+ })
+ }
+}
+
+func (){
+ input := []string{"a", "b", "c"}
+ output := []string{}
+ // ok:exported_loop_pointer
+ for _, val := range input {
+ output = append(output, val)
+ }
+}
diff --git a/go/lang/correctness/looppointer.yaml b/go/lang/correctness/looppointer.yaml
new file mode 100644
index 00000000..61d5139f
--- /dev/null
+++ b/go/lang/correctness/looppointer.yaml
@@ -0,0 +1,29 @@
+rules:
+ - id: exported_loop_pointer
+ message: >-
+ `$VALUE` is a loop pointer that may be exported from the loop. This pointer is
+ shared between loop iterations, so the exported reference will always point to
+ the last loop value, which is likely unintentional. To fix, copy the pointer to
+ a new pointer within the loop.
+ metadata:
+ references:
+ - https://github.com/kyoh86/looppointer
+ category: correctness
+ technology:
+ - go
+ severity: WARNING
+ languages:
+ - go
+ pattern-either:
+ - pattern: |
+ for _, $VALUE := range $SOURCE {
+ <... &($VALUE) ...>
+ }
+ - pattern: |
+ for _, $VALUE := range $SOURCE {
+ <... func() { <... &$VALUE ...> } ...>
+ }
+ - pattern: |
+ for _, $VALUE := range $SOURCE {
+ <... $ANYTHING(..., <... &$VALUE ...>, ...) ...>
+ }
diff --git a/go/lang/correctness/overflow/overflow.go b/go/lang/correctness/overflow/overflow.go
new file mode 100644
index 00000000..ea8c432b
--- /dev/null
+++ b/go/lang/correctness/overflow/overflow.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "fmt"
+ "strconv"
+)
+
+func mainInt16Ex1() {
+ // ruleid: integer-overflow-int16
+ bigValue, err := strconv.Atoi("2147483648")
+ if err != nil {
+ panic(err)
+ }
+ value := int16(bigValue)
+ fmt.Println(value)
+}
+
+func mainInt16Ex2() {
+ // ok: integer-overflow-int16
+ bigValue, err := strconv.Atoi("10")
+ if err != nil {
+ panic(err)
+ }
+ value := int16(bigValue)
+ fmt.Println(value)
+}
+
+func mainInt32Ex1() {
+ // ruleid: integer-overflow-int32
+ bigValue, err := strconv.Atoi("2147483648")
+ if err != nil {
+ panic(err)
+ }
+ value := int32(bigValue)
+ fmt.Println(value)
+}
+
+func mainInt32Ex2() {
+ // ok: integer-overflow-int32
+ bigValue, err := strconv.Atoi("10")
+ if err != nil {
+ panic(err)
+ }
+ value := int32(bigValue)
+ fmt.Println(value)
+}
+
+func main() {
+ mainInt16Ex1()
+ mainInt16Ex2()
+ mainInt32Ex1()
+ mainInt32Ex2()
+}
diff --git a/go/lang/correctness/overflow/overflow.yaml b/go/lang/correctness/overflow/overflow.yaml
new file mode 100644
index 00000000..09c753fb
--- /dev/null
+++ b/go/lang/correctness/overflow/overflow.yaml
@@ -0,0 +1,39 @@
+rules:
+ - id: integer-overflow-int16
+ message:
+ Detected conversion of the result of a strconv.Atoi command to an int16. This could lead to an integer overflow,
+ which could possibly result in unexpected behavior and even privilege escalation. Instead, use `strconv.ParseInt`.
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern: |
+ $F, $ERR := strconv.Atoi($NUM)
+ ...
+ int16($F)
+ - metavariable-comparison:
+ metavariable: $NUM
+ comparison: $NUM > 32767 or $NUM < -32768
+ strip: true
+ metadata:
+ category: correctness
+ technology:
+ - go
+ - id: integer-overflow-int32
+ message:
+ Detected conversion of the result of a strconv.Atoi command to an int32. This could lead to an integer overflow,
+ which could possibly result in unexpected behavior and even privilege escalation. Instead, use `strconv.ParseInt`.
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern: |
+ $F, $ERR := strconv.Atoi($NUM)
+ ...
+ int32($F)
+ - metavariable-comparison:
+ metavariable: $NUM
+ comparison: $NUM > 2147483647 or $NUM < -2147483648
+ strip: true
+ metadata:
+ category: correctness
+ technology:
+ - go
diff --git a/go/lang/correctness/permissions/file_permission.fixed.go b/go/lang/correctness/permissions/file_permission.fixed.go
new file mode 100644
index 00000000..ba86795b
--- /dev/null
+++ b/go/lang/correctness/permissions/file_permission.fixed.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+func main() {
+}
+
+func test_chmod() {
+ // ruleid: incorrect-default-permission
+ err := os.Chmod("/tmp/somefile", 0600)
+ if err != nil {
+ fmt.Println("Error when changing file permissions!")
+ return
+ }
+
+ // ok: incorrect-default-permission
+ err := os.Chmod("/tmp/somefile", 0400)
+ if err != nil {
+ fmt.Println("Error when changing file permissions!")
+ return
+ }
+}
+
+func test_mkdir() {
+ // ruleid: incorrect-default-permission
+ err := os.Mkdir("/tmp/mydir", 0600)
+ if err != nil {
+ fmt.Println("Error when creating a directory!")
+ return
+ }
+
+ // ruleid: incorrect-default-permission
+ err = os.MkdirAll("/tmp/mydir", 0600)
+ if err != nil {
+ fmt.Println("Error when creating a directory!")
+ return
+ }
+
+ // ok: incorrect-default-permission
+ err := os.MkdirAll("/tmp/mydir", 0600)
+ if err != nil {
+ fmt.Println("Error when creating a directory!")
+ return
+ }
+}
+
+func test_openfile() {
+ // ruleid: incorrect-default-permission
+ _, err := os.OpenFile("/tmp/thing", os.O_CREATE|os.O_WRONLY, 0600)
+ if err != nil {
+ fmt.Println("Error opening a file!")
+ return
+ }
+
+ // ok: incorrect-default-permission
+ _, err := os.OpenFile("/tmp/thing", os.O_CREATE|os.O_WRONLY, 0600)
+ if err != nil {
+ fmt.Println("Error opening a file!")
+ return
+ }
+}
+
+func test_writefile() {
+ // ruleid: incorrect-default-permission
+ err := ioutil.WriteFile("/tmp/demo2", []byte("This is some data"), 0600)
+ if err != nil {
+ fmt.Println("Error while writing!")
+ }
+}
diff --git a/go/lang/correctness/permissions/file_permission.go b/go/lang/correctness/permissions/file_permission.go
new file mode 100644
index 00000000..c68c0292
--- /dev/null
+++ b/go/lang/correctness/permissions/file_permission.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+)
+
+func main() {
+}
+
+func test_chmod() {
+ // ruleid: incorrect-default-permission
+ err := os.Chmod("/tmp/somefile", 0777)
+ if err != nil {
+ fmt.Println("Error when changing file permissions!")
+ return
+ }
+
+ // ok: incorrect-default-permission
+ err := os.Chmod("/tmp/somefile", 0400)
+ if err != nil {
+ fmt.Println("Error when changing file permissions!")
+ return
+ }
+}
+
+func test_mkdir() {
+ // ruleid: incorrect-default-permission
+ err := os.Mkdir("/tmp/mydir", 0777)
+ if err != nil {
+ fmt.Println("Error when creating a directory!")
+ return
+ }
+
+ // ruleid: incorrect-default-permission
+ err = os.MkdirAll("/tmp/mydir", 0777)
+ if err != nil {
+ fmt.Println("Error when creating a directory!")
+ return
+ }
+
+ // ok: incorrect-default-permission
+ err := os.MkdirAll("/tmp/mydir", 0600)
+ if err != nil {
+ fmt.Println("Error when creating a directory!")
+ return
+ }
+}
+
+func test_openfile() {
+ // ruleid: incorrect-default-permission
+ _, err := os.OpenFile("/tmp/thing", os.O_CREATE|os.O_WRONLY, 0666)
+ if err != nil {
+ fmt.Println("Error opening a file!")
+ return
+ }
+
+ // ok: incorrect-default-permission
+ _, err := os.OpenFile("/tmp/thing", os.O_CREATE|os.O_WRONLY, 0600)
+ if err != nil {
+ fmt.Println("Error opening a file!")
+ return
+ }
+}
+
+func test_writefile() {
+ // ruleid: incorrect-default-permission
+ err := ioutil.WriteFile("/tmp/demo2", []byte("This is some data"), 0644)
+ if err != nil {
+ fmt.Println("Error while writing!")
+ }
+}
diff --git a/go/lang/correctness/permissions/file_permission.yaml b/go/lang/correctness/permissions/file_permission.yaml
new file mode 100644
index 00000000..3f3453c4
--- /dev/null
+++ b/go/lang/correctness/permissions/file_permission.yaml
@@ -0,0 +1,31 @@
+rules:
+ - id: incorrect-default-permission
+ message:
+ Detected file permissions that are set to more than `0600` (user/owner can read and write). Setting file permissions
+ to higher than `0600` is most likely unnecessary and violates the principle of least privilege. Instead, set permissions
+ to be `0600` or less for os.Chmod, os.Mkdir, os.OpenFile, os.MkdirAll, and ioutil.WriteFile
+ metadata:
+ cwe: "CWE-276: Incorrect Default Permissions"
+ source_rule_url: https://github.com/securego/gosec
+ category: correctness
+ references:
+ - https://github.com/securego/gosec/blob/master/rules/fileperms.go
+ technology:
+ - go
+ severity: WARNING
+ languages: [go]
+ patterns:
+ - pattern-either:
+ - pattern: os.Chmod($NAME, $PERM)
+ - pattern: os.Mkdir($NAME, $PERM)
+ - pattern: os.OpenFile($NAME, $FLAG, $PERM)
+ - pattern: os.MkdirAll($NAME, $PERM)
+ - pattern: ioutil.WriteFile($NAME, $DATA, $PERM)
+ - metavariable-comparison:
+ metavariable: $PERM
+ comparison: $PERM > 0o600
+ base: 8
+ - focus-metavariable:
+ - $PERM
+ fix: |
+ 0600
diff --git a/go/lang/correctness/use-filepath-join.go b/go/lang/correctness/use-filepath-join.go
new file mode 100644
index 00000000..3d344fad
--- /dev/null
+++ b/go/lang/correctness/use-filepath-join.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "filepath"
+ "path"
+)
+
+func a() {
+ dir := getDir()
+
+ // ok: use-filepath-join
+ var p = path.Join(getDir())
+ // ok: use-filepath-join
+ var fpath = filepath.Join(getDir())
+
+ // ruleid: use-filepath-join
+ path.Join("/", path.Base(p))
+}
+
+func a() {
+ url, err := url.Parse("http://foo:666/bar")
+ if err != nil {
+ panic(err)
+ }
+
+ // ok: use-filepath-join
+ fmt.Println(path.Join(url.Path, "baz"))
+}
+
+func a(p string) {
+ // ruleid: use-filepath-join
+ fmt.Println(path.Join(p, "baz"))
+
+ // ok: use-filepath-join
+ fmt.Println(path.Join("asdf", "baz"))
+
+ // ok: use-filepath-join
+ fmt.Println(filepath.Join(a.Path, "baz"))
+}
+
diff --git a/go/lang/correctness/use-filepath-join.yaml b/go/lang/correctness/use-filepath-join.yaml
new file mode 100644
index 00000000..288ca0bb
--- /dev/null
+++ b/go/lang/correctness/use-filepath-join.yaml
@@ -0,0 +1,50 @@
+rules:
+ - id: use-filepath-join
+ languages:
+ - go
+ severity: WARNING
+ message: "`path.Join(...)` always joins using a forward slash. This may cause
+ issues on Windows or other systems using a different delimiter. Use
+ `filepath.Join(...)` instead which uses OS-specific path separators."
+ metadata:
+ category: correctness
+ references:
+ - https://parsiya.net/blog/2019-03-09-path.join-considered-harmful/
+ - https://go.dev/src/path/path.go?s=4034:4066#L145
+ likelihood: LOW
+ impact: HIGH
+ confidence: LOW
+ subcategory:
+ - audit
+ technology:
+ - go
+ mode: taint
+ pattern-sources:
+ - patterns:
+ - pattern: |
+ ($STR : string)
+ - pattern-not: |
+ "..."
+ - patterns:
+ - pattern-inside: |
+ import "path"
+ ...
+ - pattern: path.$FUNC(...)
+ - metavariable-regex:
+ metavariable: $FUNC
+ regex: ^(Base|Clean|Dir|Split)$
+ - patterns:
+ - pattern-inside: |
+ import "path/filepath"
+ ...
+ - pattern: filepath.$FUNC(...)
+ - metavariable-regex:
+ metavariable: $FUNC
+ regex: ^(Base|Clean|Dir|FromSlash|Glob|Rel|Split|SplitList|ToSlash|VolumeName)$
+ pattern-sinks:
+ - pattern: path.Join(...)
+ pattern-sanitizers:
+ - pattern: |
+ url.Parse(...)
+ ...
+
diff --git a/go/lang/correctness/useless-eqeq.go b/go/lang/correctness/useless-eqeq.go
new file mode 100644
index 00000000..8959ed27
--- /dev/null
+++ b/go/lang/correctness/useless-eqeq.go
@@ -0,0 +1,16 @@
+package main
+import "fmt"
+
+func main() {
+ fmt.Println("hello world")
+ var y = "hello";
+ // ruleid:eqeq-is-bad
+ fmt.Println(y == y)
+ // ok:eqeq-is-bad
+ assert(y == y)
+
+ // ruleid:hardcoded-eq-true-or-false
+ if (false) {
+ fmt.Println("never")
+ }
+}
diff --git a/go/lang/correctness/useless-eqeq.yaml b/go/lang/correctness/useless-eqeq.yaml
new file mode 100644
index 00000000..c39fe022
--- /dev/null
+++ b/go/lang/correctness/useless-eqeq.yaml
@@ -0,0 +1,31 @@
+rules:
+ - id: eqeq-is-bad
+ patterns:
+ - pattern-not-inside: assert(...)
+ - pattern-either:
+ - pattern: $X == $X
+ - pattern: $X != $X
+ - pattern-not: 1 == 1
+ message:
+ Detected useless comparison operation `$X == $X` or `$X != $X`. This will always return 'True' or 'False' and therefore
+ is not necessary. Instead, remove this comparison operation or use another comparison expression that is not deterministic.
+ languages: [go]
+ severity: INFO
+ metadata:
+ category: correctness
+ technology:
+ - go
+ - id: hardcoded-eq-true-or-false
+ message:
+ Detected useless if statement. 'if (True)' and 'if (False)' always result in the same behavior, and therefore is
+ not necessary in the code. Remove the 'if (False)' expression completely or just the 'if (True)' comparison depending
+ on which expression is in the code.
+ languages: [go]
+ severity: INFO
+ pattern-either:
+ - pattern: if (true) { ... }
+ - pattern: if (false) { ... }
+ metadata:
+ category: correctness
+ technology:
+ - go
diff --git a/go/lang/maintainability/useless-ifelse.go b/go/lang/maintainability/useless-ifelse.go
new file mode 100644
index 00000000..1dc85f7e
--- /dev/null
+++ b/go/lang/maintainability/useless-ifelse.go
@@ -0,0 +1,33 @@
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("hello world")
+ var y = 1
+
+ if y {
+ fmt.Println("of course")
+ }
+
+ // ruleid:useless-if-conditional
+ if y {
+ fmt.Println("of course")
+ } else if y {
+ fmt.Println("of course other thing")
+ }
+
+ // ruleid:useless-if-body
+ if y {
+ fmt.Println("of course")
+ } else {
+ fmt.Println("of course")
+ }
+
+ fmt.Println("of course2")
+ fmt.Println(1)
+ fmt.Println(2)
+ fmt.Println(3)
+ fmt.Println("of course2")
+
+}
diff --git a/go/lang/maintainability/useless-ifelse.yaml b/go/lang/maintainability/useless-ifelse.yaml
new file mode 100644
index 00000000..f5b9b4bb
--- /dev/null
+++ b/go/lang/maintainability/useless-ifelse.yaml
@@ -0,0 +1,33 @@
+rules:
+ - id: useless-if-conditional
+ message:
+ Detected an if block that checks for the same condition on both branches (`$X`). The second condition check is
+ useless as it is the same as the first, and therefore can be removed from the code,
+ languages: [go]
+ severity: WARNING
+ pattern: |
+ if ($X) {
+ ...
+ } else if ($X) {
+ ...
+ }
+ metadata:
+ category: maintainability
+ technology:
+ - go
+ - id: useless-if-body
+ pattern: |
+ if ($X) {
+ $S
+ } else {
+ $S
+ }
+ message:
+ Detected identical statements in the if body and the else body of an if-statement. This will lead to the same code
+ being executed no matter what the if-expression evaluates to. Instead, remove the if statement.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ category: maintainability
+ technology:
+ - go
diff --git a/go/lang/security/audit/crypto/bad_imports.go b/go/lang/security/audit/crypto/bad_imports.go
new file mode 100644
index 00000000..b7bedfe5
--- /dev/null
+++ b/go/lang/security/audit/crypto/bad_imports.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "crypto/cipher"
+ "crypto/des"
+ "crypto/md5"
+ "crypto/rand"
+ "crypto/rc4"
+ "crypto/sha1"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/cgi"
+ "os"
+)
+
+func main1() {
+ // ruleid: insecure-module-used
+ cgi.Serve(http.FileServer(http.Dir("/usr/share/doc")))
+}
+
+func main2() {
+ // ok: insecure-module-used
+ block, err := des.NewCipher([]byte("sekritz"))
+ if err != nil {
+ panic(err)
+ }
+ plaintext := []byte("I CAN HAZ SEKRIT MSG PLZ")
+ ciphertext := make([]byte, des.BlockSize+len(plaintext))
+ iv := ciphertext[:des.BlockSize]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ panic(err)
+ }
+ stream := cipher.NewCFBEncrypter(block, iv)
+ stream.XORKeyStream(ciphertext[des.BlockSize:], plaintext)
+ fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
+}
+
+func main3() {
+ for _, arg := range os.Args {
+ // ok: insecure-module-used
+ fmt.Printf("%x - %s\n", md5.Sum([]byte(arg)), arg)
+ }
+}
+
+func main4() {
+ // ok: insecure-module-used
+ cipher, err := rc4.NewCipher([]byte("sekritz"))
+ if err != nil {
+ panic(err)
+ }
+ plaintext := []byte("I CAN HAZ SEKRIT MSG PLZ")
+ ciphertext := make([]byte, len(plaintext))
+ cipher.XORKeyStream(ciphertext, plaintext)
+ fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
+}
+
+func main5() {
+ for _, arg := range os.Args {
+ // ok: insecure-module-used
+ fmt.Printf("%x - %s\n", sha1.Sum([]byte(arg)), arg)
+ }
+}
diff --git a/go/lang/security/audit/crypto/bad_imports.yaml b/go/lang/security/audit/crypto/bad_imports.yaml
new file mode 100644
index 00000000..59cfdc21
--- /dev/null
+++ b/go/lang/security/audit/crypto/bad_imports.yaml
@@ -0,0 +1,32 @@
+rules:
+- id: insecure-module-used
+ message: >-
+ The package `net/http/cgi` is on the import blocklist.
+ The package is vulnerable to httpoxy attacks (CVE-2015-5386).
+ It is recommended to use `net/http` or a web framework to build a web application instead.
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ source-rule-url: https://github.com/securego/gosec
+ references:
+ - https://godoc.org/golang.org/x/crypto/sha3
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: MEDIUM
+ impact: MEDIUM
+ languages: [go]
+ severity: WARNING
+ pattern-either:
+ - patterns:
+ - pattern-inside: |
+ import "net/http/cgi"
+ ...
+ - pattern: |
+ cgi.$FUNC(...)
diff --git a/go/lang/security/audit/crypto/insecure_ssh.go b/go/lang/security/audit/crypto/insecure_ssh.go
new file mode 100644
index 00000000..9074aba6
--- /dev/null
+++ b/go/lang/security/audit/crypto/insecure_ssh.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "golang.org/x/crypto/ssh"
+)
+
+func ok() {
+ var publicKey *rsa.PublicKey
+
+ privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ return nil, nil, err
+ }
+ publicKey = &privateKey.PublicKey
+ hostKey, _ := ssh.NewPublicKey(publicKey)
+ // ok: avoid-ssh-insecure-ignore-host-key
+ _ = ssh.FixedHostKey(hostKey);
+}
+
+func main() {
+ // ruleid: avoid-ssh-insecure-ignore-host-key
+ _ = ssh.InsecureIgnoreHostKey()
+}
diff --git a/go/lang/security/audit/crypto/insecure_ssh.yaml b/go/lang/security/audit/crypto/insecure_ssh.yaml
new file mode 100644
index 00000000..206b4747
--- /dev/null
+++ b/go/lang/security/audit/crypto/insecure_ssh.yaml
@@ -0,0 +1,29 @@
+rules:
+- id: avoid-ssh-insecure-ignore-host-key
+ message: >-
+ Disabled host key verification detected. This allows man-in-the-middle
+ attacks. Use the 'golang.org/x/crypto/ssh/knownhosts' package to do
+ host key verification.
+ See https://skarlso.github.io/2019/02/17/go-ssh-with-host-key-verification/
+ to learn more about the problem and how to fix it.
+ metadata:
+ cwe:
+ - 'CWE-322: Key Exchange without Entity Authentication'
+ owasp:
+ - A02:2021 - Cryptographic Failures
+ source-rule-url: https://github.com/securego/gosec
+ references:
+ - https://skarlso.github.io/2019/02/17/go-ssh-with-host-key-verification/
+ - https://gist.github.com/Skarlso/34321a230cf0245018288686c9e70b2d
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ languages: [go]
+ severity: WARNING
+ pattern: |-
+ ssh.InsecureIgnoreHostKey()
diff --git a/go/lang/security/audit/crypto/math_random.fixed.go b/go/lang/security/audit/crypto/math_random.fixed.go
new file mode 100644
index 00000000..66f18e42
--- /dev/null
+++ b/go/lang/security/audit/crypto/math_random.fixed.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "crypto/rand"
+ // ruleid: math-random-used
+ mrand "crypto/rand"
+ // ruleid: math-random-used
+ mrand "crypto/rand"
+ // ruleid: math-random-used
+ mrand "crypto/rand"
+ // ok: math-random-used
+ mrand "math/rand/something"
+)
+
+func main() {
+ main0()
+ main1()
+ main2()
+ main3()
+}
+
+func main0() {
+ // ok: math-random-used
+ bad, _ := mrand.Read(nil)
+ println(bad)
+}
+
+func main1() {
+ // ok: math-random-used
+ good, _ := rand.Read(nil)
+ println(good)
+}
+
+func main2() {
+ // ok: math-random-used
+ bad := mrand.Int()
+ println(bad)
+}
+
+func main3() {
+ // ok: math-random-used
+ good, _ := rand.Read(nil)
+ println(good)
+ i := mrand.Int31()
+ println(i)
+}
diff --git a/go/lang/security/audit/crypto/math_random.go b/go/lang/security/audit/crypto/math_random.go
new file mode 100644
index 00000000..7192833e
--- /dev/null
+++ b/go/lang/security/audit/crypto/math_random.go
@@ -0,0 +1,46 @@
+package main
+
+import (
+ "crypto/rand"
+ // ruleid: math-random-used
+ mrand "math/rand"
+ // ruleid: math-random-used
+ mrand "math/rand/v2"
+ // ruleid: math-random-used
+ mrand "math/rand/v222"
+ // ok: math-random-used
+ mrand "math/rand/something"
+)
+
+func main() {
+ main0()
+ main1()
+ main2()
+ main3()
+}
+
+func main0() {
+ // ok: math-random-used
+ bad, _ := mrand.Read(nil)
+ println(bad)
+}
+
+func main1() {
+ // ok: math-random-used
+ good, _ := rand.Read(nil)
+ println(good)
+}
+
+func main2() {
+ // ok: math-random-used
+ bad := mrand.Int()
+ println(bad)
+}
+
+func main3() {
+ // ok: math-random-used
+ good, _ := rand.Read(nil)
+ println(good)
+ i := mrand.Int31()
+ println(i)
+}
diff --git a/go/lang/security/audit/crypto/math_random.yaml b/go/lang/security/audit/crypto/math_random.yaml
new file mode 100644
index 00000000..feef3816
--- /dev/null
+++ b/go/lang/security/audit/crypto/math_random.yaml
@@ -0,0 +1,40 @@
+rules:
+- id: math-random-used
+ metadata:
+ cwe:
+ - 'CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)'
+ owasp:
+ - A02:2021 - Cryptographic Failures
+ references:
+ - https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#secure-random-number-generation
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ message: Do not use `math/rand`. Use `crypto/rand` instead.
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-either:
+ - pattern: |
+ import $RAND "$MATH"
+ - pattern: |
+ import "$MATH"
+ - metavariable-regex:
+ metavariable: $MATH
+ regex: ^(math/rand(\/v[0-9]+)*)$
+ - pattern-either:
+ - pattern-inside: |
+ ...
+ rand.$FUNC(...)
+ - pattern-inside: |
+ ...
+ $RAND.$FUNC(...)
+ - focus-metavariable:
+ - $MATH
+ fix: |
+ crypto/rand
diff --git a/go/lang/security/audit/crypto/missing-ssl-minversion.fixed.go b/go/lang/security/audit/crypto/missing-ssl-minversion.fixed.go
new file mode 100644
index 00000000..681da872
--- /dev/null
+++ b/go/lang/security/audit/crypto/missing-ssl-minversion.fixed.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "crypto/tls"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "os"
+)
+
+// zeroSource is an io.Reader that returns an unlimited number of zero bytes.
+type zeroSource struct{}
+
+func (zeroSource) Read(b []byte) (n int, err error) {
+ for i := range b {
+ b[i] = 0
+ }
+
+ return len(b), nil
+}
+
+func main() {
+ // Dummy test HTTP server for the example with insecure random so output is
+ // reproducible.
+ server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+ // ruleid: missing-ssl-minversion
+ server.TLS = &tls.Config{ Rand: zeroSource{}, MinVersion: tls.VersionTLS13 }
+ server.StartTLS()
+ defer server.Close()
+
+ // Typically the log would go to an open file:
+ // w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+ w := os.Stdout
+
+ client := &http.Client{
+ Transport: &http.Transport{
+ // ok: missing-ssl-minversion
+ TLSClientConfig: &tls.Config{
+ KeyLogWriter: w,
+ MinVersion: tls.VersionSSL30,
+ Rand: zeroSource{}, // for reproducible output; don't do this.
+ InsecureSkipVerify: true, // test server certificate is not trusted.
+ },
+ },
+ }
+ resp, err := client.Get(server.URL)
+ if err != nil {
+ log.Fatalf("Failed to get URL: %v", err)
+ }
+ resp.Body.Close()
+
+ clientGood := &http.Client{
+ Transport: &http.Transport{
+ // ok: missing-ssl-minversion
+ TLSClientConfig: &tls.Config{
+ KeyLogWriter: w,
+ MinVersion: tls.VersionTLS10,
+ Rand: zeroSource{}, // for reproducible output; don't do this.
+ InsecureSkipVerify: true, // test server certificate is not trusted.
+ },
+ },
+ }
+ resp, err := client.Get(server.URL)
+ if err != nil {
+ log.Fatalf("Failed to get URL: %v", err)
+ }
+ resp.Body.Close()
+}
diff --git a/go/lang/security/audit/crypto/missing-ssl-minversion.go b/go/lang/security/audit/crypto/missing-ssl-minversion.go
new file mode 100644
index 00000000..cd4ab1c6
--- /dev/null
+++ b/go/lang/security/audit/crypto/missing-ssl-minversion.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "crypto/tls"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "os"
+)
+
+// zeroSource is an io.Reader that returns an unlimited number of zero bytes.
+type zeroSource struct{}
+
+func (zeroSource) Read(b []byte) (n int, err error) {
+ for i := range b {
+ b[i] = 0
+ }
+
+ return len(b), nil
+}
+
+func main() {
+ // Dummy test HTTP server for the example with insecure random so output is
+ // reproducible.
+ server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+ // ruleid: missing-ssl-minversion
+ server.TLS = &tls.Config{
+ Rand: zeroSource{}, // for example only; don't do this.
+ }
+ server.StartTLS()
+ defer server.Close()
+
+ // Typically the log would go to an open file:
+ // w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+ w := os.Stdout
+
+ client := &http.Client{
+ Transport: &http.Transport{
+ // ok: missing-ssl-minversion
+ TLSClientConfig: &tls.Config{
+ KeyLogWriter: w,
+ MinVersion: tls.VersionSSL30,
+ Rand: zeroSource{}, // for reproducible output; don't do this.
+ InsecureSkipVerify: true, // test server certificate is not trusted.
+ },
+ },
+ }
+ resp, err := client.Get(server.URL)
+ if err != nil {
+ log.Fatalf("Failed to get URL: %v", err)
+ }
+ resp.Body.Close()
+
+ clientGood := &http.Client{
+ Transport: &http.Transport{
+ // ok: missing-ssl-minversion
+ TLSClientConfig: &tls.Config{
+ KeyLogWriter: w,
+ MinVersion: tls.VersionTLS10,
+ Rand: zeroSource{}, // for reproducible output; don't do this.
+ InsecureSkipVerify: true, // test server certificate is not trusted.
+ },
+ },
+ }
+ resp, err := client.Get(server.URL)
+ if err != nil {
+ log.Fatalf("Failed to get URL: %v", err)
+ }
+ resp.Body.Close()
+}
diff --git a/go/lang/security/audit/crypto/missing-ssl-minversion.yaml b/go/lang/security/audit/crypto/missing-ssl-minversion.yaml
new file mode 100644
index 00000000..fdbcf08f
--- /dev/null
+++ b/go/lang/security/audit/crypto/missing-ssl-minversion.yaml
@@ -0,0 +1,38 @@
+rules:
+- id: missing-ssl-minversion
+ message: >-
+ `MinVersion` is missing from this TLS configuration.
+ By default, TLS 1.2 is currently used as the minimum when acting as a client, and TLS 1.0 when acting as a server.
+ General purpose web applications should default to TLS 1.3 with all other protocols disabled.
+ Only where it is known that a web server must support legacy clients
+ with unsupported an insecure browsers (such as Internet Explorer 10), it may be necessary to enable TLS 1.0 to provide support.
+ Add `MinVersion: tls.VersionTLS13' to the TLS configuration to bump the minimum version to TLS 1.3.
+ metadata:
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ source-rule-url: https://github.com/securego/gosec/blob/master/rules/tls_config.go
+ references:
+ - https://golang.org/doc/go1.14#crypto/tls
+ - https://golang.org/pkg/crypto/tls/#:~:text=MinVersion
+ - https://www.us-cert.gov/ncas/alerts/TA14-290A
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ subcategory:
+ - audit
+ likelihood: MEDIUM
+ impact: LOW
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern: |
+ tls.Config{ $...CONF }
+ - pattern-not: |
+ tls.Config{..., MinVersion: ..., ...}
+ fix: |
+ tls.Config{ $...CONF, MinVersion: tls.VersionTLS13 }
+
diff --git a/go/lang/security/audit/crypto/sha224-hash.go b/go/lang/security/audit/crypto/sha224-hash.go
new file mode 100644
index 00000000..09d8d97b
--- /dev/null
+++ b/go/lang/security/audit/crypto/sha224-hash.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "crypto/sha256"
+ "golang.org/x/crypto/sha3"
+ "fmt"
+ "io"
+ "log"
+ "os"
+)
+
+func main() {
+}
+
+func test_sha224() {
+ f, err := os.Open("file.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ // ruleid: sha224-hash
+ h := sha256.New224()
+ if _, err := io.Copy(h, f); err != nil {
+ log.Fatal(err)
+ }
+ // ruleid: sha224-hash
+ fmt.Printf("%x", sha256.Sum224(nil))
+}
+
+func test_sha3_224() {
+ f, err := os.Open("file.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ // ruleid: sha224-hash
+ h := sha3.New224()
+ if _, err := io.Copy(h, f); err != nil {
+ log.Fatal(err)
+ }
+ // ruleid: sha224-hash
+ fmt.Printf("%x", sha3.Sum224(nil))
+}
\ No newline at end of file
diff --git a/go/lang/security/audit/crypto/sha224-hash.yaml b/go/lang/security/audit/crypto/sha224-hash.yaml
new file mode 100644
index 00000000..8fe39e05
--- /dev/null
+++ b/go/lang/security/audit/crypto/sha224-hash.yaml
@@ -0,0 +1,44 @@
+rules:
+- id: sha224-hash
+ pattern-either:
+ - patterns:
+ - pattern-inside: |
+ import "crypto/sha256"
+ ...
+ - pattern-either:
+ - pattern: |
+ sha256.New224()
+ - pattern: |
+ sha256.Sum224(...)
+ - patterns:
+ - pattern-inside: |
+ import "golang.org/x/crypto/sha3"
+ ...
+ - pattern-either:
+ - pattern: |
+ sha3.New224()
+ - pattern: |
+ sha3.Sum224(...)
+ message: >-
+ This code uses a 224-bit hash function, which is deprecated or disallowed
+ in some security policies. Consider updating to a stronger hash function such
+ as SHA-384 or higher to ensure compliance and security.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-328: Use of Weak Hash'
+ category: security
+ technology:
+ - go
+ references:
+ - https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar3.ipd.pdf
+ - https://www.cyber.gov.au/resources-business-and-government/essential-cyber-security/ism/cyber-security-guidelines/guidelines-cryptography
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: LOW
+ confidence: HIGH
\ No newline at end of file
diff --git a/go/lang/security/audit/crypto/ssl.go b/go/lang/security/audit/crypto/ssl.go
new file mode 100644
index 00000000..d69d77ec
--- /dev/null
+++ b/go/lang/security/audit/crypto/ssl.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "crypto/tls"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "os"
+)
+
+// zeroSource is an io.Reader that returns an unlimited number of zero bytes.
+type zeroSource struct{}
+
+func (zeroSource) Read(b []byte) (n int, err error) {
+ for i := range b {
+ b[i] = 0
+ }
+
+ return len(b), nil
+}
+
+func main() {
+ // Dummy test HTTP server for the example with insecure random so output is
+ // reproducible.
+ server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
+ server.TLS = &tls.Config{
+ Rand: zeroSource{}, // for example only; don't do this.
+ }
+ server.StartTLS()
+ defer server.Close()
+
+ // Typically the log would go to an open file:
+ // w, err := os.OpenFile("tls-secrets.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
+ w := os.Stdout
+
+ client := &http.Client{
+ Transport: &http.Transport{
+ // ruleid: ssl-v3-is-insecure
+ TLSClientConfig: &tls.Config{
+ KeyLogWriter: w,
+ MinVersion: tls.VersionSSL30,
+ Rand: zeroSource{}, // for reproducible output; don't do this.
+ InsecureSkipVerify: true, // test server certificate is not trusted.
+ },
+ },
+ }
+ resp, err := client.Get(server.URL)
+ if err != nil {
+ log.Fatalf("Failed to get URL: %v", err)
+ }
+ resp.Body.Close()
+
+ client_good := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{
+ KeyLogWriter: w,
+ // OK
+ MinVersion: tls.VersionTLS10,
+ Rand: zeroSource{}, // for reproducible output; don't do this.
+ InsecureSkipVerify: true, // test server certificate is not trusted.
+ },
+ },
+ }
+ resp, err := client.Get(server.URL)
+ if err != nil {
+ log.Fatalf("Failed to get URL: %v", err)
+ }
+ resp.Body.Close()
+}
diff --git a/go/lang/security/audit/crypto/ssl.yaml b/go/lang/security/audit/crypto/ssl.yaml
new file mode 100644
index 00000000..330a34b9
--- /dev/null
+++ b/go/lang/security/audit/crypto/ssl.yaml
@@ -0,0 +1,30 @@
+rules:
+- id: ssl-v3-is-insecure
+ message: >-
+ SSLv3 is insecure because it has known vulnerabilities.
+ Starting with go1.14, SSLv3 will be removed. Instead, use
+ 'tls.VersionTLS13'.
+ metadata:
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ source-rule-url: https://github.com/securego/gosec/blob/master/rules/tls_config.go
+ references:
+ - https://golang.org/doc/go1.14#crypto/tls
+ - https://www.us-cert.gov/ncas/alerts/TA14-290A
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: LOW
+ languages: [go]
+ severity: WARNING
+ fix-regex:
+ regex: VersionSSL30
+ replacement: VersionTLS13
+ pattern: 'tls.Config{..., MinVersion: $TLS.VersionSSL30, ...}'
diff --git a/go/lang/security/audit/crypto/tls.go b/go/lang/security/audit/crypto/tls.go
new file mode 100644
index 00000000..af84aa4b
--- /dev/null
+++ b/go/lang/security/audit/crypto/tls.go
@@ -0,0 +1,32 @@
+// Insecure ciphersuite selection
+package main
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/http"
+)
+
+func main() {
+ tr := &http.Transport{
+ // ruleid: tls-with-insecure-cipher
+ TLSClientConfig: &tls.Config{CipherSuites: []uint16{
+ tls.TLS_RSA_WITH_RC4_128_SHA,
+ tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
+ }},
+ }
+ client := &http.Client{Transport: tr}
+ _, err := client.Get("https://golang.org/")
+ if err != nil {
+ fmt.Println(err)
+ }
+
+ tr := &http.Transport{
+ // should be fine
+ TLSClientConfig: &tls.Config{CipherSuites: []uint16{
+ tls.TLS_AES_128_GCM_SHA256,
+ tls.TLS_AES_256_GCM_SHA384,
+ }},
+ }
+ client := &http.Client{Transport: tr}
+}
diff --git a/go/lang/security/audit/crypto/tls.yaml b/go/lang/security/audit/crypto/tls.yaml
new file mode 100644
index 00000000..c02dad3f
--- /dev/null
+++ b/go/lang/security/audit/crypto/tls.yaml
@@ -0,0 +1,60 @@
+rules:
+- id: tls-with-insecure-cipher
+ message: >-
+ Detected an insecure CipherSuite via the 'tls' module. This suite is considered
+ weak.
+ Use the function 'tls.CipherSuites()' to get a list of good cipher suites.
+ See https://golang.org/pkg/crypto/tls/#InsecureCipherSuites
+ for why and what other cipher suites to use.
+ metadata:
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ source-rule-url: https://github.com/securego/gosec/blob/master/rules/tls.go
+ references:
+ - https://golang.org/pkg/crypto/tls/#InsecureCipherSuites
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ subcategory:
+ - vuln
+ likelihood: HIGH
+ impact: LOW
+ languages: [go]
+ severity: WARNING
+ pattern-either:
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_RSA_WITH_RC4_128_SHA, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_RSA_WITH_AES_128_CBC_SHA256, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ...}}
+ - pattern: |
+ tls.Config{..., CipherSuites: []$TYPE{..., tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, ...}}
+ - pattern: |
+ tls.CipherSuite{..., TLS_RSA_WITH_RC4_128_SHA, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_RSA_WITH_3DES_EDE_CBC_SHA, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_RSA_WITH_AES_128_CBC_SHA256, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_ECDHE_RSA_WITH_RC4_128_SHA, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ...}
+ - pattern: |
+ tls.CipherSuite{..., TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, ...}
diff --git a/go/lang/security/audit/crypto/use_of_weak_crypto.go b/go/lang/security/audit/crypto/use_of_weak_crypto.go
new file mode 100644
index 00000000..ca544bce
--- /dev/null
+++ b/go/lang/security/audit/crypto/use_of_weak_crypto.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+ "crypto/des"
+ "crypto/md5"
+ "crypto/rc4"
+ "crypto/sha1"
+ "fmt"
+ "io"
+ "log"
+ "os"
+)
+
+func main() {
+}
+
+func test_des() {
+ // NewTripleDESCipher can also be used when EDE2 is required by
+ // duplicating the first 8 bytes of the 16-byte key.
+ ede2Key := []byte("example key 1234")
+
+ var tripleDESKey []byte
+ tripleDESKey = append(tripleDESKey, ede2Key[:16]...)
+ tripleDESKey = append(tripleDESKey, ede2Key[:8]...)
+ // ruleid: use-of-DES
+ _, err := des.NewTripleDESCipher(tripleDESKey)
+ if err != nil {
+ panic(err)
+ }
+
+ // See crypto/cipher for how to use a cipher.Block for encryption and
+ // decryption.
+}
+
+func test_md5() {
+ f, err := os.Open("file.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+
+ defer func() {
+ err := f.Close()
+ if err != nil {
+ log.Printf("error closing the file: %s", err)
+ }
+ }()
+
+ // ruleid: use-of-md5
+ h := md5.New()
+ if _, err := io.Copy(h, f); err != nil {
+ log.Fatal(err)
+ }
+ // ruleid: use-of-md5
+ fmt.Printf("%x", md5.Sum(nil))
+}
+
+func test_rc4() {
+ key := []byte{1, 2, 3, 4, 5, 6, 7}
+ // ruleid: use-of-rc4
+ c, err := rc4.NewCipher(key)
+ dst := make([]byte, len(src))
+ c.XORKeyStream(dst, src)
+}
+
+func test_sha1() {
+ f, err := os.Open("file.txt")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ // ruleid: use-of-sha1
+ h := sha1.New()
+ if _, err := io.Copy(h, f); err != nil {
+ log.Fatal(err)
+ }
+ // ruleid: use-of-sha1
+ fmt.Printf("%x", sha1.Sum(nil))
+}
\ No newline at end of file
diff --git a/go/lang/security/audit/crypto/use_of_weak_crypto.yaml b/go/lang/security/audit/crypto/use_of_weak_crypto.yaml
new file mode 100644
index 00000000..3c8e6175
--- /dev/null
+++ b/go/lang/security/audit/crypto/use_of_weak_crypto.yaml
@@ -0,0 +1,128 @@
+rules:
+- id: use-of-md5
+ message: >-
+ Detected MD5 hash algorithm which is considered insecure. MD5 is not
+ collision resistant and is therefore not suitable as a cryptographic
+ signature. Use SHA256 or SHA3 instead.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-328: Use of Weak Hash'
+ source-rule-url: https://github.com/securego/gosec#available-rules
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ references:
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ patterns:
+ - pattern-inside: |
+ import "crypto/md5"
+ ...
+ - pattern-either:
+ - pattern: |
+ md5.New()
+ - pattern: |
+ md5.Sum(...)
+- id: use-of-sha1
+ message: >-
+ Detected SHA1 hash algorithm which is considered insecure. SHA1 is not
+ collision resistant and is therefore not suitable as a cryptographic
+ signature. Use SHA256 or SHA3 instead.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-328: Use of Weak Hash'
+ source-rule-url: https://github.com/securego/gosec#available-rules
+ category: security
+ technology:
+ - go
+ references:
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ confidence: MEDIUM
+ patterns:
+ - pattern-inside: |
+ import "crypto/sha1"
+ ...
+ - pattern-either:
+ - pattern: |
+ sha1.New()
+ - pattern: |
+ sha1.Sum(...)
+- id: use-of-DES
+ message: >-
+ Detected DES cipher algorithm which is insecure. The algorithm is
+ considered weak and has been deprecated. Use AES instead.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ source-rule-url: https://github.com/securego/gosec#available-rules
+ category: security
+ technology:
+ - go
+ references:
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ confidence: MEDIUM
+ patterns:
+ - pattern-inside: |
+ import "crypto/des"
+ ...
+ - pattern-either:
+ - pattern: |
+ des.NewTripleDESCipher(...)
+ - pattern: |
+ des.NewCipher(...)
+- id: use-of-rc4
+ message: >-
+ Detected RC4 cipher algorithm which is insecure. The algorithm has many
+ known vulnerabilities. Use AES instead.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ source-rule-url: https://github.com/securego/gosec#available-rules
+ category: security
+ technology:
+ - go
+ references:
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ confidence: MEDIUM
+ patterns:
+ - pattern-inside: |
+ import "crypto/rc4"
+ ...
+ - pattern: |-
+ rc4.NewCipher(...)
diff --git a/go/lang/security/audit/crypto/use_of_weak_rsa_key.fixed.go b/go/lang/security/audit/crypto/use_of_weak_rsa_key.fixed.go
new file mode 100644
index 00000000..5e0846cc
--- /dev/null
+++ b/go/lang/security/audit/crypto/use_of_weak_rsa_key.fixed.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "fmt"
+)
+
+func main() {
+ //Generate Private Key
+ // ruleid: use-of-weak-rsa-key
+ pvk, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(pvk)
+
+ // ok: use-of-weak-rsa-key
+ pvk, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(pvk)
+}
diff --git a/go/lang/security/audit/crypto/use_of_weak_rsa_key.go b/go/lang/security/audit/crypto/use_of_weak_rsa_key.go
new file mode 100644
index 00000000..0c792586
--- /dev/null
+++ b/go/lang/security/audit/crypto/use_of_weak_rsa_key.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "crypto/rand"
+ "crypto/rsa"
+ "fmt"
+)
+
+func main() {
+ //Generate Private Key
+ // ruleid: use-of-weak-rsa-key
+ pvk, err := rsa.GenerateKey(rand.Reader, 1024)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(pvk)
+
+ // ok: use-of-weak-rsa-key
+ pvk, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ fmt.Println(err)
+ }
+ fmt.Println(pvk)
+}
diff --git a/go/lang/security/audit/crypto/use_of_weak_rsa_key.yaml b/go/lang/security/audit/crypto/use_of_weak_rsa_key.yaml
new file mode 100644
index 00000000..8a35aa7b
--- /dev/null
+++ b/go/lang/security/audit/crypto/use_of_weak_rsa_key.yaml
@@ -0,0 +1,35 @@
+rules:
+- id: use-of-weak-rsa-key
+ message: RSA keys should be at least 2048 bits
+ languages: [go]
+ severity: WARNING
+ metadata:
+ cwe:
+ - 'CWE-326: Inadequate Encryption Strength'
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ source-rule-url: https://github.com/securego/gosec/blob/master/rules/rsa.go
+ references:
+ - https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#algorithms
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ subcategory:
+ - audit
+ likelihood: HIGH
+ impact: MEDIUM
+ patterns:
+ - pattern-either:
+ - pattern: |
+ rsa.GenerateKey(..., $BITS)
+ - pattern: |
+ rsa.GenerateMultiPrimeKey(..., $BITS)
+ - metavariable-comparison:
+ metavariable: $BITS
+ comparison: $BITS < 2048
+ - focus-metavariable:
+ - $BITS
+ fix: |
+ 2048
diff --git a/go/lang/security/audit/dangerous-command-write.go b/go/lang/security/audit/dangerous-command-write.go
new file mode 100644
index 00000000..5bb14c01
--- /dev/null
+++ b/go/lang/security/audit/dangerous-command-write.go
@@ -0,0 +1,30 @@
+ import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+func test1(password string) {
+ cmd := exec.Command("bash")
+ cmdWriter, _ := cmd.StdinPipe()
+ cmd.Start()
+
+ cmdString := fmt.Sprintf("sshpass -p %s", password)
+
+ // ruleid:dangerous-command-write
+ cmdWriter.Write([]byte(cmdString + "\n"))
+
+ cmd.Wait()
+}
+
+func okTest1() {
+ cmd := exec.Command("bash")
+ cmdWriter, _ := cmd.StdinPipe()
+ cmd.Start()
+
+ // ok:dangerous-command-write
+ cmdWriter.Write([]byte("sshpass -p 123\n"))
+ cmdWriter.Write([]byte("exit" + "\n"))
+
+ cmd.Wait()
+}
diff --git a/go/lang/security/audit/dangerous-command-write.yaml b/go/lang/security/audit/dangerous-command-write.yaml
new file mode 100644
index 00000000..fc1b72a5
--- /dev/null
+++ b/go/lang/security/audit/dangerous-command-write.yaml
@@ -0,0 +1,48 @@
+rules:
+- id: dangerous-command-write
+ patterns:
+ - pattern: |
+ $CW.Write($BYTE)
+ - pattern-inside: |
+ $CW,$ERR := $CMD.StdinPipe()
+ ...
+ - pattern-not: |
+ $CW.Write("...")
+ - pattern-not: |
+ $CW.Write([]byte("..."))
+ - pattern-not: |
+ $CW.Write([]byte("..."+"..."))
+ - pattern-not-inside: |
+ $BYTE = []byte("...");
+ ...
+ - pattern-not-inside: |
+ $BYTE = []byte("..."+"...");
+ ...
+ - pattern-inside: |
+ import "os/exec"
+ ...
+ message: >-
+ Detected non-static command inside Write. Audit the input to '$CW.Write'.
+ If unverified user data can reach this call site, this is a code injection
+ vulnerability. A malicious actor can inject a malicious script to execute
+ arbitrary code.
+ severity: ERROR
+ languages: [go]
+ metadata:
+ cwe:
+ - "CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')"
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
diff --git a/go/lang/security/audit/dangerous-exec-cmd.go b/go/lang/security/audit/dangerous-exec-cmd.go
new file mode 100644
index 00000000..41a23e53
--- /dev/null
+++ b/go/lang/security/audit/dangerous-exec-cmd.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+)
+
+func test1(userInput string) {
+
+ cmdPath,_ := userInput;
+
+ // ruleid:dangerous-exec-cmd
+ cmd := &exec.Cmd {
+ Path: cmdPath,
+ Args: []string{ "foo", "bar" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ cmd.Start();
+
+}
+
+func test2(userInput string) {
+
+ cmdPath,_ := exec.LookPath("foo");
+
+ // ruleid:dangerous-exec-cmd
+ cmd := &exec.Cmd {
+ Path: cmdPath,
+ Args: []string{ userInput, "bar" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ cmd.Start();
+
+}
+
+func test3(userInput string) {
+
+ cmdPath,_ := exec.LookPath("bash");
+
+ // ruleid:dangerous-exec-cmd
+ cmd := &exec.Cmd {
+ Path: cmdPath,
+ Args: []string{ cmdPath, "-c", userInput },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ cmd.Start();
+
+}
+
+func test4(userInput string) {
+
+ cmdPath,_ := exec.LookPath("bash");
+
+ args = []string{ cmdPath, "-c", userInput }
+
+ // ruleid:dangerous-exec-cmd
+ cmd := &exec.Cmd {
+ Path: cmdPath,
+ Args: args,
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ cmd.Start();
+
+}
+
+func okTest1(userInput string) {
+
+ cmdPath,_ := exec.LookPath("go");
+
+ // ok:dangerous-exec-cmd
+ cmd := &exec.Cmd {
+ Path: cmdPath,
+ Args: []string{ cmdPath, "bar" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ cmd.Start();
+
+}
diff --git a/go/lang/security/audit/dangerous-exec-cmd.yaml b/go/lang/security/audit/dangerous-exec-cmd.yaml
new file mode 100644
index 00000000..18309121
--- /dev/null
+++ b/go/lang/security/audit/dangerous-exec-cmd.yaml
@@ -0,0 +1,85 @@
+rules:
+- id: dangerous-exec-cmd
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern: |
+ exec.Cmd {...,Path: $CMD,...}
+ - pattern-not: |
+ exec.Cmd {...,Path: "...",...}
+ - pattern-not-inside: |
+ $CMD,$ERR := exec.LookPath("...");
+ ...
+ - pattern-not-inside: |
+ $CMD = "...";
+ ...
+ - patterns:
+ - pattern: |
+ exec.Cmd {...,Args: $ARGS,...}
+ - pattern-not: |
+ exec.Cmd {...,Args: []string{...},...}
+ - pattern-not-inside: |
+ $ARGS = []string{"...",...};
+ ...
+ - pattern-not-inside: |
+ $CMD = "...";
+ ...
+ $ARGS = []string{$CMD,...};
+ ...
+ - pattern-not-inside: |
+ $CMD = exec.LookPath("...");
+ ...
+ $ARGS = []string{$CMD,...};
+ ...
+ - patterns:
+ - pattern: |
+ exec.Cmd {...,Args: []string{$CMD,...},...}
+ - pattern-not: |
+ exec.Cmd {...,Args: []string{"...",...},...}
+ - pattern-not-inside: |
+ $CMD,$ERR := exec.LookPath("...");
+ ...
+ - pattern-not-inside: |
+ $CMD = "...";
+ ...
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ exec.Cmd {...,Args: []string{"=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$EXE,...},...}
+ - patterns:
+ - pattern: |
+ exec.Cmd {...,Args: []string{$CMD,"-c",$EXE,...},...}
+ - pattern-inside: |
+ $CMD,$ERR := exec.LookPath("=~/(sh|bash|ksh|csh|tcsh|zsh)/");
+ ...
+ - pattern-not: |
+ exec.Cmd {...,Args: []string{"...","...","...",...},...}
+ - pattern-not-inside: |
+ $EXE = "...";
+ ...
+ - pattern-inside: |
+ import "os/exec"
+ ...
+ message: >-
+ Detected non-static command inside exec.Cmd. Audit the input to 'exec.Cmd'.
+ If unverified user data can reach this call site, this is a code injection
+ vulnerability. A malicious actor can inject a malicious script to execute
+ arbitrary code.
+ metadata:
+ cwe:
+ - "CWE-94: Improper Control of Generation of Code ('Code Injection')"
+ owasp:
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
+ languages: [go]
diff --git a/go/lang/security/audit/dangerous-exec-command.go b/go/lang/security/audit/dangerous-exec-command.go
new file mode 100644
index 00000000..0109f8c2
--- /dev/null
+++ b/go/lang/security/audit/dangerous-exec-command.go
@@ -0,0 +1,133 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "os/exec"
+ "time"
+)
+
+func runCommand1(userInput string) {
+ // ruleid:dangerous-exec-command
+ cmd := exec.Command(userInput, "foobar")
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stdout
+
+ if err := cmd.Run(); err != nil {
+ fmt.Println("Error:", err)
+ }
+
+}
+
+func runCommand2(userInput string) {
+
+ execPath, _ := exec.LookPath(userInput)
+
+ // ruleid:dangerous-exec-command
+ cmd := exec.Command(execPath, "foobar")
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stdout
+
+ if err := cmd.Run(); err != nil {
+ fmt.Println("Error:", err)
+ }
+
+}
+
+func runCommand3(userInput string) {
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+ defer cancel()
+
+ // ruleid:dangerous-exec-command
+ if err := exec.CommandContext(ctx, userInput, "5").Run(); err != nil {
+ fmt.Println("Error:", err)
+ }
+
+}
+
+func runCommand4(userInput string) {
+
+ // ruleid:dangerous-exec-command
+ cmd := exec.Command("bash", "-c", userInput)
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stdout
+
+ if err := cmd.Run(); err != nil {
+ fmt.Println("Error:", err)
+ }
+
+}
+
+func runcommand5(s string) (string, error) {
+
+ // ruleid:dangerous-exec-command
+ cmd := exec.Command("/usr/bin/env", "bash", "-c", s)
+ stdoutStderr, err := cmd.CombinedOutput()
+
+ if err != nil {
+ return "", fmt.Errorf("shellCommand: unexpected error: out = %s, error = %v", stdoutStderr, err)
+ }
+
+ return string(stdoutStderr), nil
+}
+
+func runcommand6(s string) (string, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+ // might not have user context
+ // ruleid:dangerous-exec-command
+ cmd := exec.CommandContext(ctx, "/bin/env", "bash", "-c", s)
+ stdoutStderr, err := cmd.CombinedOutput()
+
+ if err != nil {
+ return "", fmt.Errorf("shellCommand: unexpected error: out = %s, error = %v", stdoutStderr, err)
+ }
+
+ return string(stdoutStderr), nil
+}
+
+func okCommand1(userInput string) {
+
+ goExec, _ := exec.LookPath("go")
+
+ // ok:dangerous-exec-command
+ cmd := exec.Command(goExec, "version")
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stdout
+
+ if err := cmd.Run(); err != nil {
+ fmt.Println("Error:", err)
+ }
+
+}
+
+func okCommand2(userInput string) {
+ // ok:dangerous-exec-command
+ cmd := exec.Command("go", "version")
+
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stdout
+
+ if err := cmd.Run(); err != nil {
+ fmt.Println("Error:", err)
+ }
+
+}
+
+func okCommand3(s string) (string, error) {
+
+ someCommand := "w"
+ // ok:dangerous-exec-command
+ cmd := exec.Command("/usr/bin/env", "bash", "-c", someCommand)
+ stdoutStderr, err := cmd.CombinedOutput()
+
+ if err != nil {
+ return "", fmt.Errorf("shellCommand: unexpected error: out = %s, error = %v", stdoutStderr, err)
+ }
+
+ return string(stdoutStderr), nil
+}
diff --git a/go/lang/security/audit/dangerous-exec-command.yaml b/go/lang/security/audit/dangerous-exec-command.yaml
new file mode 100644
index 00000000..30630293
--- /dev/null
+++ b/go/lang/security/audit/dangerous-exec-command.yaml
@@ -0,0 +1,61 @@
+rules:
+- id: dangerous-exec-command
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ exec.Command($CMD,...)
+ - pattern: |
+ exec.CommandContext($CTX,$CMD,...)
+ - pattern-not: |
+ exec.Command("...",...)
+ - pattern-not: |
+ exec.CommandContext($CTX,"...",...)
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ exec.Command("=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$CMD,...)
+ - pattern: |
+ exec.CommandContext($CTX,"=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$CMD,...)
+ - pattern-not: |
+ exec.Command("...","...","...",...)
+ - pattern-not: |
+ exec.CommandContext($CTX,"...","...","...",...)
+ - pattern-either:
+ - pattern: |
+ exec.Command("=~/\/bin\/env/","=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$CMD,...)
+ - pattern: |
+ exec.CommandContext($CTX,"=~/\/bin\/env/","=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$CMD,...)
+ - pattern-inside: |
+ import "os/exec"
+ ...
+ - pattern-not-inside: |
+ $CMD,$ERR := exec.LookPath("...");
+ ...
+ - pattern-not-inside: |
+ $CMD = "...";
+ ...
+ message: >-
+ Detected non-static command inside Command. Audit the input to 'exec.Command'.
+ If unverified user data can reach this call site, this is a code injection
+ vulnerability. A malicious actor can inject a malicious script to execute
+ arbitrary code.
+ metadata:
+ cwe:
+ - "CWE-94: Improper Control of Generation of Code ('Code Injection')"
+ owasp:
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
+ languages: [go]
diff --git a/go/lang/security/audit/dangerous-syscall-exec.go b/go/lang/security/audit/dangerous-syscall-exec.go
new file mode 100644
index 00000000..ae5fc536
--- /dev/null
+++ b/go/lang/security/audit/dangerous-syscall-exec.go
@@ -0,0 +1,80 @@
+package main
+
+import "syscall"
+import "os"
+import "os/exec"
+
+func test1(userInput string) {
+
+ binary, lookErr := exec.LookPath(userInput)
+ if lookErr != nil {
+ panic(lookErr)
+ }
+
+ args := []string{"ls", "-a", "-l", "-h"}
+
+ env := os.Environ()
+
+ // ruleid:dangerous-syscall-exec
+ execErr := syscall.Exec(binary, args, env)
+ if execErr != nil {
+ panic(execErr)
+ }
+}
+
+
+func test2(userInput string) {
+
+ binary, lookErr := exec.LookPath("sh")
+ if lookErr != nil {
+ panic(lookErr)
+ }
+
+ args := []string{userInput, "-a", "-l", "-h"}
+
+ env := os.Environ()
+
+ // ruleid:dangerous-syscall-exec
+ execErr := syscall.Exec(binary, args, env)
+ if execErr != nil {
+ panic(execErr)
+ }
+}
+
+func test3(userInput string) {
+
+ binary, lookErr := exec.LookPath("sh")
+ if lookErr != nil {
+ panic(lookErr)
+ }
+
+ args := []string{binary, "-c", userInput}
+
+ env := os.Environ()
+
+ // ruleid:dangerous-syscall-exec
+ execErr := syscall.Exec(binary, args, env)
+ if execErr != nil {
+ panic(execErr)
+ }
+}
+
+
+
+func okTest1(userInput string) {
+
+ binary, lookErr := exec.LookPath("ls")
+ if lookErr != nil {
+ panic(lookErr)
+ }
+
+ args := []string{"ls", "-a", "-l", "-h"}
+
+ env := os.Environ()
+
+ // ok:dangerous-syscall-exec
+ execErr := syscall.Exec(binary, args, env)
+ if execErr != nil {
+ panic(execErr)
+ }
+}
diff --git a/go/lang/security/audit/dangerous-syscall-exec.yaml b/go/lang/security/audit/dangerous-syscall-exec.yaml
new file mode 100644
index 00000000..1a66818b
--- /dev/null
+++ b/go/lang/security/audit/dangerous-syscall-exec.yaml
@@ -0,0 +1,97 @@
+rules:
+- id: dangerous-syscall-exec
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern: |
+ syscall.$METHOD($BIN,...)
+ - pattern-not: |
+ syscall.$METHOD("...",...)
+ - pattern-not-inside: |
+ $BIN,$ERR := exec.LookPath("...");
+ ...
+ - pattern-not-inside: |
+ $BIN = "...";
+ ...
+ - patterns:
+ - pattern: |
+ syscall.$METHOD($BIN,$ARGS,...)
+ - pattern-not: |
+ syscall.$METHOD($BIN,[]string{"...",...},...)
+ - pattern-not-inside: |
+ $ARGS := []string{"...",...};
+ ...
+ - pattern-not-inside: |
+ $CMD = "...";
+ ...
+ $ARGS = []string{$CMD,...};
+ ...
+ - pattern-not-inside: |
+ $CMD,$ERR := exec.LookPath("...");
+ ...
+ $ARGS = []string{$CMD,...};
+ ...
+ - patterns:
+ - pattern: |
+ syscall.$METHOD($BIN,[]string{"=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$EXE,...},...)
+ - pattern-not: |
+ syscall.$METHOD($BIN,[]string{"...","...","...",...},...)
+ - patterns:
+ - pattern: |
+ syscall.$METHOD($BIN,$ARGS,...)
+ - pattern-either:
+ - pattern-inside: |
+ $ARGS := []string{"=~/(sh|bash|ksh|csh|tcsh|zsh)/","-c",$EXE,...};
+ ...
+ - pattern-inside: |
+ $CMD = "=~/(sh|bash|ksh|csh|tcsh|zsh)/";
+ ...
+ $ARGS = []string{$CMD,"-c",$EXE,...};
+ ...
+ - pattern-inside: |
+ $CMD,$ERR := exec.LookPath("=~/(sh|bash|ksh|csh|tcsh|zsh)/");
+ ...
+ $ARGS = []string{$CMD,"-c",$EXE,...};
+ ...
+ - pattern-not-inside: |
+ $ARGS := []string{"...","...","...",...};
+ ...
+ - pattern-not-inside: |
+ $CMD = "...";
+ ...
+ $ARGS = []string{$CMD,"...","...",...};
+ ...
+ - pattern-not-inside: |
+ $CMD,$ERR := exec.LookPath("...");
+ ...
+ $ARGS = []string{$CMD,"...","...",...};
+ ...
+ - pattern-inside: |
+ import "syscall"
+ ...
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: (Exec|ForkExec)
+ message: >-
+ Detected non-static command inside Exec. Audit the input to 'syscall.Exec'.
+ If unverified user data can reach this call site, this is a code injection
+ vulnerability. A malicious actor can inject a malicious script to execute
+ arbitrary code.
+ metadata:
+ cwe:
+ - "CWE-94: Improper Control of Generation of Code ('Code Injection')"
+ owasp:
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
+ languages: [go]
diff --git a/go/lang/security/audit/database/string-formatted-query.go b/go/lang/security/audit/database/string-formatted-query.go
new file mode 100644
index 00000000..4197bde8
--- /dev/null
+++ b/go/lang/security/audit/database/string-formatted-query.go
@@ -0,0 +1,277 @@
+package main
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "http"
+
+ "github.com/jackc/pgx/v4"
+)
+
+var db *sql.DB
+var postgresDb *pgx.Conn
+
+func dbExec(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
+
+ row, _ := db.Exec(query)
+
+ // ok: string-formatted-query
+ out, err := sshClient.Exec(fmt.Sprintf("sudo bash %s", scriptPath))
+}
+
+func okDbExec(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ // ok: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = customerId"
+
+ row, _ := db.Exec(query)
+}
+
+func dbQuery1(r *http.Request) {
+ // ruleid: string-formatted-query
+ _, err = db.Query("INSERT into users (username, password) VALUES(" + username + ", " + password)
+ if err != nil {
+ http.Error("mistake")
+ }
+}
+
+func dbQuery2(r *http.Request, username string, password string) {
+ // ruleid: string-formatted-query
+ query = "INSERT into users (username, password) VALUES(" + username + ", " + password
+ _, err = db.QueryRow(query)
+ if err != nil {
+ http.Error("mistake")
+ }
+}
+
+func dbQuery3(r *http.Request, username string) {
+ // ruleid: string-formatted-query
+ query = username + " AND INSERT into users (username, password)"
+ _, err = db.Exec(query)
+ if err != nil {
+ http.Error("mistake")
+ }
+}
+
+func dbQuery4(r *http.Request, username string) {
+ // ruleid: string-formatted-query
+ query := fmt.Sprintf("%s AND INSERT into users (username, password)", username)
+ _, err = db.Exec(query)
+ if err != nil {
+ http.Error("mistake")
+ }
+}
+
+func dbQuery5(r *http.Request, username string, password string) {
+ // ruleid: string-formatted-query
+ query := fmt.Sprintf("INSERT into users (username, password) VALUES(%s, %s)", username, password)
+ _, err = db.QueryRow(query)
+ if err != nil {
+ http.Error("mistake")
+ }
+}
+
+func okDbQuery1(r *http.Request) {
+ // ok: string-formatted-query
+ _, err = db.Exec("INSERT into users (username, password) VALUES(" + "username" + ", " + "smth)")
+ if err != nil {
+ http.Error("mistake")
+ }
+}
+
+func dbExecContext(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
+
+ row, _ := db.ExecContext(ctx, query)
+}
+
+func dbQuery4(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
+
+ row, _ := db.Query(query)
+}
+
+func dbQueryContext(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
+
+ row, _ := db.QueryContext(ctx, query)
+}
+
+func dbQueryRow(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
+
+ row, _ := db.QueryRow(query)
+}
+
+func dbQueryRowContext(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId
+
+ row, _ := db.QueryRowContext(ctx, query)
+}
+
+func dbExecFmt(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s"
+ // ruleid: string-formatted-query
+ query = fmt.Printf(query, customerId)
+
+ row, _ := db.Exec(query)
+}
+
+func dbExecContextFmt(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s"
+ // ruleid: string-formatted-query
+ query = fmt.Printf(query, customerId)
+
+ row, _ := db.ExecContext(ctx, query)
+}
+
+func dbQueryFmt(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s"
+ // ruleid: string-formatted-query
+ query = fmt.Printf(query, customerId)
+
+ row, _ := db.Query(query)
+}
+
+func dbQueryContextFmtReassign(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s"
+ // ruleid: string-formatted-query
+ query = fmt.Printf(query, customerId)
+
+ row, _ := db.QueryContext(ctx, query)
+}
+
+
+func dbQueryContextFmt(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := fmt.Sprintf("SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s", customerId)
+ row, _ := db.QueryContext(ctx, query)
+}
+
+func dbQueryRowFmt(r *http.Request) {
+ customerId := r.URL.Query().Get("id")
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s"
+ // ruleid: string-formatted-query
+ query = fmt.Printf(query, customerId)
+
+ row, _ := db.QueryRow(query)
+}
+
+func dbQueryRowContextReassign(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s"
+ // ruleid: string-formatted-query
+ query = fmt.Printf(query, customerId)
+
+ row, _ := db.QueryRowContext(ctx, query)
+}
+
+func dbQueryRowContextFmt(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := fmt.Sprintf("SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s", customerId)
+
+ row, _ := db.QueryRowContext(ctx, query)
+}
+
+func unmodifiedString() {
+ // ok: string-formatted-query
+ query := "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = 1234"
+ row, _ := db.Query(query)
+}
+
+func unmodifiedStringDirectly() {
+ // ok: string-formatted-query
+ row, _ := db.Query("SELECT number, expireDate, cvv FROM creditcards WHERE customerId = 1234")
+}
+
+func badDirectQueryAdd(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: string-formatted-query
+ row, _ := db.QueryRowContext(ctx, "SELECT number, expireDate, cvv FROM creditcards WHERE customerId = " + customerId)
+}
+
+func badDirectQueryFmt(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: string-formatted-query
+ row, _ := db.QueryRowContext(ctx, fmt.Printf("SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s", customerId))
+}
+
+func postgresBadDirectQueryFmt(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: string-formatted-query
+ row, _ := postgresDb.QueryRow(ctx, fmt.Printf("SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s", customerId))
+}
+
+func postgresQueryFmt(r *http.Request) {
+ ctx := context.Background()
+ customerId := r.URL.Query().Get("id")
+ // ruleid: string-formatted-query
+ query := fmt.Sprintf("SELECT number, expireDate, cvv FROM creditcards WHERE customerId = %s", customerId)
+
+ row, _ := postgresDb.QueryRow(ctx, query)
+}
+
+package main
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "http"
+
+ "github.com/jackc/pgx/v4"
+)
+// cf. https://github.com/returntocorp/semgrep-rules/issues/1249
+func new() {
+ // ok: string-formatted-query
+ var insertSql string = "insert into t_ad_experiment (exp_layer,buckets,opposite_buckets,is_transparent, " +
+ " description,is_full,start_time,end_time,creat_time,update_time,update_user,white_list,extra,status)" +
+ " value (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
+ t := time.Now().Unix()
+ InsertResult, err := DbConn.Exec(insertSql, info.Exp_layer, info.Buckets, info.Opposite_buckets,
+ info.Is_transparent, info.Description, info.Is_full, info.Start_time, info.End_time, t, t,
+ session.User, info.White_list, info.Extra, 0)
+}
+
+func new2() {
+ // ok: string-formatted-query
+ var insertSql string = "insert into t_ad_experiment (exp_layer,buckets,opposite_buckets,is_transparent, description,is_full,start_time,end_time,creat_time,update_time,update_user,white_list,extra,status) value (?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
+ t := time.Now().Unix()
+ InsertResult, err := DbConn.Exec(insertSql, info.Exp_layer, info.Buckets, info.Opposite_buckets,
+ info.Is_transparent, info.Description, info.Is_full, info.Start_time, info.End_time, t, t,
+ session.User, info.White_list, info.Extra, 0)
+}
diff --git a/go/lang/security/audit/database/string-formatted-query.yaml b/go/lang/security/audit/database/string-formatted-query.yaml
new file mode 100644
index 00000000..7aeb388a
--- /dev/null
+++ b/go/lang/security/audit/database/string-formatted-query.yaml
@@ -0,0 +1,107 @@
+rules:
+- id: string-formatted-query
+ languages: [go]
+ message: >-
+ String-formatted SQL query detected. This could lead to SQL injection if
+ the string is not sanitized properly. Audit this call to ensure the
+ SQL is not manipulable by external data.
+ severity: WARNING
+ metadata:
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ cwe:
+ - "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
+ source-rule-url: https://github.com/securego/gosec
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
+ patterns:
+ - metavariable-regex:
+ metavariable: $OBJ
+ regex: (?i).*(db|database)
+ - pattern-not-inside: |
+ $VAR = "..." + "..."
+ ...
+ $OBJ.$SINK(..., $VAR, ...)
+ - pattern-not: $OBJ.Exec("...")
+ - pattern-not: $OBJ.ExecContext($CTX, "...")
+ - pattern-not: $OBJ.Query("...")
+ - pattern-not: $OBJ.QueryContext($CTX, "...")
+ - pattern-not: $OBJ.QueryRow("...")
+ - pattern-not: $OBJ.QueryRow($CTX, "...")
+ - pattern-not: $OBJ.QueryRowContext($CTX, "...")
+ - pattern-either:
+ - pattern: $OBJ.Exec($X + ...)
+ - pattern: $OBJ.ExecContext($CTX, $X + ...)
+ - pattern: $OBJ.Query($X + ...)
+ - pattern: $OBJ.QueryContext($CTX, $X + ...)
+ - pattern: $OBJ.QueryRow($X + ...)
+ - pattern: $OBJ.QueryRow($CTX, $X + ...)
+ - pattern: $OBJ.QueryRowContext($CTX, $X + ...)
+ - pattern: $OBJ.Exec(fmt.$P("...", ...))
+ - pattern: $OBJ.ExecContext($CTX, fmt.$P("...", ...))
+ - pattern: $OBJ.Query(fmt.$P("...", ...))
+ - pattern: $OBJ.QueryContext($CTX, fmt.$P("...", ...))
+ - pattern: $OBJ.QueryRow(fmt.$P("...", ...))
+ - pattern: $OBJ.QueryRow($CTX, fmt.$U("...", ...))
+ - pattern: $OBJ.QueryRowContext($CTX, fmt.$P("...", ...))
+ - patterns:
+ - pattern-either:
+ - pattern: $QUERY = fmt.Fprintf($F, "$SQLSTR", ...)
+ - pattern: $QUERY = fmt.Sprintf("$SQLSTR", ...)
+ - pattern: $QUERY = fmt.Printf("$SQLSTR", ...)
+ - pattern: $QUERY = $X + ...
+ - pattern-either:
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.Query($QUERY, ...)
+ ...
+ }
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.ExecContext($CTX, $QUERY, ...)
+ ...
+ }
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.Exec($QUERY, ...)
+ ...
+ }
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.QueryRow($CTX, $QUERY)
+ ...
+ }
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.QueryRow($QUERY)
+ ...
+ }
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.QueryContext($CTX, $QUERY)
+ ...
+ }
+ - pattern-inside: |
+ func $FUNC(...) {
+ ...
+ $OBJ.QueryRowContext($CTX, $QUERY, ...)
+ ...
+ }
+
\ No newline at end of file
diff --git a/go/lang/security/audit/md5-used-as-password.go b/go/lang/security/audit/md5-used-as-password.go
new file mode 100644
index 00000000..e1c29e27
--- /dev/null
+++ b/go/lang/security/audit/md5-used-as-password.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "crypto/md5"
+ "crypto/sha256"
+ "fmt"
+ "io"
+)
+
+//// True positives ////
+func ex1(user *User, pwtext string) {
+ h := md5.New()
+ io.WriteString(h, pwtext)
+ // ruleid: md5-used-as-password
+ user.setPassword(h.Sum(nil))
+}
+
+func ex2(user *User, pwtext string) {
+ data := []byte(pwtext)
+ // ruleid: md5-used-as-password
+ user.setPassword(md5.Sum(data))
+}
+
+//// True negatives ////
+func ok1(user *User, pwtext string) {
+ h := sha256.New()
+ io.WriteString(h, pwtext)
+ // ok: md5-used-as-password
+ user.setPassword(h.Sum(nil))
+}
+
+func ok2(user *User, pwtext string) {
+ data := []byte(pwtext)
+ // ok: md5-used-as-password
+ user.setPassword(sha256.Sum(data))
+}
+
+func ok3(user *User, pwtext string) {
+ data := []byte(pwtext)
+ // ok: md5-used-as-password
+ user.setSomethingElse(md5.Sum(data))
+}
diff --git a/go/lang/security/audit/md5-used-as-password.yaml b/go/lang/security/audit/md5-used-as-password.yaml
new file mode 100644
index 00000000..b2d42f92
--- /dev/null
+++ b/go/lang/security/audit/md5-used-as-password.yaml
@@ -0,0 +1,43 @@
+rules:
+- id: md5-used-as-password
+ languages: [go]
+ severity: WARNING
+ message: >-
+ It looks like MD5 is used as a password hash. MD5 is not considered a
+ secure password hash because it can be cracked by an attacker in a short
+ amount of time. Use a suitable password hashing function such as bcrypt.
+ You can use the `golang.org/x/crypto/bcrypt` package.
+ options:
+ interfile: true
+ metadata:
+ category: security
+ technology:
+ - md5
+ references:
+ - https://tools.ietf.org/id/draft-lvelvindron-tls-md5-sha1-deprecate-01.html
+ - https://security.stackexchange.com/questions/211/how-to-securely-hash-passwords
+ - https://github.com/returntocorp/semgrep-rules/issues/1609
+ - https://pkg.go.dev/golang.org/x/crypto/bcrypt
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - 'CWE-327: Use of a Broken or Risky Cryptographic Algorithm'
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ interfile: true
+ mode: taint
+ pattern-sources:
+ - patterns:
+ - pattern-either:
+ - pattern: md5.New
+ - pattern: md5.Sum
+ pattern-sinks:
+ - patterns:
+ - pattern: $FUNCTION(...)
+ - metavariable-regex:
+ metavariable: $FUNCTION
+ regex: (?i)(.*password.*)
diff --git a/go/lang/security/audit/net/bind_all.go b/go/lang/security/audit/net/bind_all.go
new file mode 100644
index 00000000..38b2708e
--- /dev/null
+++ b/go/lang/security/audit/net/bind_all.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+ "log"
+ "net"
+)
+
+func bind_all() {
+ // ruleid: avoid-bind-to-all-interfaces
+ l, err := net.Listen("tcp", "0.0.0.0:2000")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+}
+
+func bind_default() {
+ // ruleid: avoid-bind-to-all-interfaces
+ l, err := net.Listen("tcp", ":2000")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+}
+
+func main() {
+ // ok: avoid-bind-to-all-interfaces
+ l, err := net.Listen("tcp", "192.168.1.101:2000")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+}
diff --git a/go/lang/security/audit/net/bind_all.yaml b/go/lang/security/audit/net/bind_all.yaml
new file mode 100644
index 00000000..967ffb5d
--- /dev/null
+++ b/go/lang/security/audit/net/bind_all.yaml
@@ -0,0 +1,30 @@
+rules:
+- id: avoid-bind-to-all-interfaces
+ message: >-
+ Detected a network listener listening on 0.0.0.0 or an empty string. This could unexpectedly expose
+ the server publicly as it binds to all available interfaces. Instead, specify another IP address
+ that is not 0.0.0.0 nor the empty string.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ cwe:
+ - 'CWE-200: Exposure of Sensitive Information to an Unauthorized Actor'
+ owasp:
+ - A01:2021 - Broken Access Control
+ source-rule-url: https://github.com/securego/gosec
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ references:
+ - https://owasp.org/Top10/A01_2021-Broken_Access_Control
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ pattern-either:
+ - pattern: tls.Listen($NETWORK, "=~/^0.0.0.0:.*$/", ...)
+ - pattern: net.Listen($NETWORK, "=~/^0.0.0.0:.*$/", ...)
+ - pattern: tls.Listen($NETWORK, "=~/^:.*$/", ...)
+ - pattern: net.Listen($NETWORK, "=~/^:.*$/", ...)
diff --git a/go/lang/security/audit/net/bind_all_default.go b/go/lang/security/audit/net/bind_all_default.go
new file mode 100644
index 00000000..7ad2f2fe
--- /dev/null
+++ b/go/lang/security/audit/net/bind_all_default.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "log"
+ "net"
+)
+
+func main() {
+ // ruleid: avoid-bind-to-all-interfaces
+ l, err := net.Listen("tcp", ":2000")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer l.Close()
+}
diff --git a/go/lang/security/audit/net/cookie-missing-httponly.go b/go/lang/security/audit/net/cookie-missing-httponly.go
new file mode 100644
index 00000000..84e5dbf7
--- /dev/null
+++ b/go/lang/security/audit/net/cookie-missing-httponly.go
@@ -0,0 +1,68 @@
+// cf. https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/util/cookie.go
+
+package main
+
+import (
+ "net/http"
+ "time"
+)
+
+func SetCookieLevel(w http.ResponseWriter, r *http.Request, cookievalue string) {
+
+ level := cookievalue
+ if level == "" {
+ level = "low"
+ }
+ SetCookie(w, "Level", level)
+
+}
+
+func CheckLevel(r *http.Request) bool {
+ level := GetCookie(r, "Level")
+ if level == "" || level == "low" {
+ return false //set default level to low
+ } else if level == "high" {
+ return true //level == high
+ } else {
+ return false // level == low
+ }
+}
+
+/* cookie setter getter */
+
+func SetCookie(w http.ResponseWriter, name, value string) {
+ // ruleid: cookie-missing-httponly
+ cookie := http.Cookie{
+ Name: name,
+ Value: value,
+ }
+ http.SetCookie(w, &cookie)
+}
+
+func SetSecureCookie(w http.ResponseWriter, name, value string) {
+ // ok: cookie-missing-httponly
+ cookie := http.Cookie{
+ Secure: true,
+ HttpOnly: true,
+ Name: name,
+ Value: value,
+ }
+ http.SetCookie(w, &cookie)
+}
+
+func GetCookie(r *http.Request, name string) string {
+ cookie, _ := r.Cookie(name)
+ return cookie.Value
+}
+
+func DeleteCookie(w http.ResponseWriter, cookies []string) {
+ for _, name := range cookies {
+ // ruleid: cookie-missing-httponly
+ cookie := &http.Cookie{
+ Name: name,
+ Value: "",
+ Expires: time.Unix(0, 0),
+ }
+ http.SetCookie(w, cookie)
+ }
+}
diff --git a/go/lang/security/audit/net/cookie-missing-httponly.yaml b/go/lang/security/audit/net/cookie-missing-httponly.yaml
new file mode 100644
index 00000000..64f045c2
--- /dev/null
+++ b/go/lang/security/audit/net/cookie-missing-httponly.yaml
@@ -0,0 +1,40 @@
+rules:
+- id: cookie-missing-httponly
+ patterns:
+ - pattern-not-inside: |
+ http.Cookie{
+ ...,
+ HttpOnly: true,
+ ...,
+ }
+ - pattern: |
+ http.Cookie{
+ ...,
+ }
+ message: >-
+ A session cookie was detected without setting the 'HttpOnly' flag.
+ The 'HttpOnly' flag for cookies instructs the browser to forbid
+ client-side scripts from reading the cookie which mitigates XSS
+ attacks. Set the 'HttpOnly' flag by setting 'HttpOnly' to 'true'
+ in the Cookie.
+ metadata:
+ cwe:
+ - "CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/util/cookie.go
+ - https://golang.org/src/net/http/cookie.go
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: LOW
+ fix-regex:
+ regex: (HttpOnly\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages: [go]
diff --git a/go/lang/security/audit/net/cookie-missing-secure.go b/go/lang/security/audit/net/cookie-missing-secure.go
new file mode 100644
index 00000000..695eca45
--- /dev/null
+++ b/go/lang/security/audit/net/cookie-missing-secure.go
@@ -0,0 +1,68 @@
+// cf. https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/util/cookie.go
+
+package main
+
+import (
+ "net/http"
+ "time"
+)
+
+func SetCookieLevel(w http.ResponseWriter, r *http.Request, cookievalue string) {
+
+ level := cookievalue
+ if level == "" {
+ level = "low"
+ }
+ SetCookie(w, "Level", level)
+
+}
+
+func CheckLevel(r *http.Request) bool {
+ level := GetCookie(r, "Level")
+ if level == "" || level == "low" {
+ return false //set default level to low
+ } else if level == "high" {
+ return true //level == high
+ } else {
+ return false // level == low
+ }
+}
+
+/* cookie setter getter */
+
+func SetCookie(w http.ResponseWriter, name, value string) {
+ // ruleid: cookie-missing-secure
+ cookie := http.Cookie{
+ Name: name,
+ Value: value,
+ }
+ http.SetCookie(w, &cookie)
+}
+
+func SetSecureCookie(w http.ResponseWriter, name, value string) {
+ // ok: cookie-missing-secure
+ cookie := http.Cookie{
+ Secure: true,
+ HttpOnly: true,
+ Name: name,
+ Value: value,
+ }
+ http.SetCookie(w, &cookie)
+}
+
+func GetCookie(r *http.Request, name string) string {
+ cookie, _ := r.Cookie(name)
+ return cookie.Value
+}
+
+func DeleteCookie(w http.ResponseWriter, cookies []string) {
+ for _, name := range cookies {
+ // ruleid: cookie-missing-secure
+ cookie := &http.Cookie{
+ Name: name,
+ Value: "",
+ Expires: time.Unix(0, 0),
+ }
+ http.SetCookie(w, cookie)
+ }
+}
diff --git a/go/lang/security/audit/net/cookie-missing-secure.yaml b/go/lang/security/audit/net/cookie-missing-secure.yaml
new file mode 100644
index 00000000..88d58eca
--- /dev/null
+++ b/go/lang/security/audit/net/cookie-missing-secure.yaml
@@ -0,0 +1,39 @@
+rules:
+- id: cookie-missing-secure
+ patterns:
+ - pattern-not-inside: |
+ http.Cookie{
+ ...,
+ Secure: true,
+ ...,
+ }
+ - pattern: |
+ http.Cookie{
+ ...,
+ }
+ message: >-
+ A session cookie was detected without setting the 'Secure' flag.
+ The 'secure' flag for cookies prevents the client from transmitting
+ the cookie over insecure channels such as HTTP. Set the 'Secure'
+ flag by setting 'Secure' to 'true' in the Options struct.
+ metadata:
+ cwe:
+ - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/util/cookie.go
+ - https://golang.org/src/net/http/cookie.go
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: LOW
+ fix-regex:
+ regex: (Secure\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages: [go]
\ No newline at end of file
diff --git a/go/lang/security/audit/net/dynamic-httptrace-clienttrace-ok.go b/go/lang/security/audit/net/dynamic-httptrace-clienttrace-ok.go
new file mode 100644
index 00000000..8bc0eee8
--- /dev/null
+++ b/go/lang/security/audit/net/dynamic-httptrace-clienttrace-ok.go
@@ -0,0 +1,341 @@
+/*
+ * Test case reference:
+ * cf. https://github.com/containous/traefik//blob/bb4de11c517dfa4a6f6ca446732f4b55f771cb49/pkg/middlewares/retry/retry.go
+ */
+
+package main
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/http/httptrace"
+ "time"
+
+ "github.com/containous/traefik/v2/pkg/config/dynamic"
+ "github.com/containous/traefik/v2/pkg/log"
+ "github.com/containous/traefik/v2/pkg/middlewares"
+ "github.com/containous/traefik/v2/pkg/tracing"
+ "github.com/opentracing/opentracing-go/ext"
+)
+
+// Compile time validation that the response writer implements http interfaces correctly.
+var _ middlewares.Stateful = &responseWriterWithCloseNotify{}
+
+const (
+ typeName = "Retry"
+)
+
+// Listener is used to inform about retry attempts.
+type Listener interface {
+ // Retried will be called when a retry happens, with the request attempt passed to it.
+ // For the first retry this will be attempt 2.
+ Retried(req *http.Request, attempt int)
+}
+
+// Listeners is a convenience type to construct a list of Listener and notify
+// each of them about a retry attempt.
+type Listeners []Listener
+
+// retry is a middleware that retries requests.
+type retry struct {
+ attempts int
+ next http.Handler
+ listener Listener
+ name string
+}
+
+// New returns a new retry middleware.
+func New(ctx context.Context, next http.Handler, config dynamic.Retry, listener Listener, name string) (http.Handler, error) {
+ log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware")
+
+ if config.Attempts <= 0 {
+ return nil, fmt.Errorf("incorrect (or empty) value for attempt (%d)", config.Attempts)
+ }
+
+ return &retry{
+ attempts: config.Attempts,
+ next: next,
+ listener: listener,
+ name: name,
+ }, nil
+}
+
+func (r *retry) GetTracingInformation() (string, ext.SpanKindEnum) {
+ return r.name, tracing.SpanKindNoneEnum
+}
+
+func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
+ // if we might make multiple attempts, swap the body for an ioutil.NopCloser
+ // cf https://github.com/containous/traefik/issues/1008
+ if r.attempts > 1 {
+ body := req.Body
+ defer body.Close()
+ req.Body = ioutil.NopCloser(body)
+ }
+
+ attempts := 1
+ for {
+ shouldRetry := attempts < r.attempts
+ retryResponseWriter := newResponseWriter(rw, shouldRetry)
+
+ // Disable retries when the backend already received request data
+ trace := &httptrace.ClientTrace{
+ WroteHeaders: func() {
+ retryResponseWriter.DisableRetries()
+ },
+ WroteRequest: func(httptrace.WroteRequestInfo) {
+ retryResponseWriter.DisableRetries()
+ },
+ }
+ // ok: dynamic-httptrace-clienttrace
+ newCtx := httptrace.WithClientTrace(req.Context(), trace)
+
+ r.next.ServeHTTP(retryResponseWriter, req.WithContext(newCtx))
+
+ if !retryResponseWriter.ShouldRetry() {
+ break
+ }
+
+ attempts++
+
+ log.FromContext(middlewares.GetLoggerCtx(req.Context(), r.name, typeName)).
+ Debugf("New attempt %d for request: %v", attempts, req.URL)
+
+ r.listener.Retried(req, attempts)
+ }
+}
+
+// Retried exists to implement the Listener interface. It calls Retried on each of its slice entries.
+func (l Listeners) Retried(req *http.Request, attempt int) {
+ for _, listener := range l {
+ listener.Retried(req, attempt)
+ }
+}
+
+type responseWriter interface {
+ http.ResponseWriter
+ http.Flusher
+ ShouldRetry() bool
+ DisableRetries()
+}
+
+func newResponseWriter(rw http.ResponseWriter, shouldRetry bool) responseWriter {
+ responseWriter := &responseWriterWithoutCloseNotify{
+ responseWriter: rw,
+ headers: make(http.Header),
+ shouldRetry: shouldRetry,
+ }
+ if _, ok := rw.(http.CloseNotifier); ok {
+ return &responseWriterWithCloseNotify{
+ responseWriterWithoutCloseNotify: responseWriter,
+ }
+ }
+ return responseWriter
+}
+
+type responseWriterWithoutCloseNotify struct {
+ responseWriter http.ResponseWriter
+ headers http.Header
+ shouldRetry bool
+ written bool
+}
+
+func (r *responseWriterWithoutCloseNotify) ShouldRetry() bool {
+ return r.shouldRetry
+}
+
+func (r *responseWriterWithoutCloseNotify) DisableRetries() {
+ r.shouldRetry = false
+}
+
+func (r *responseWriterWithoutCloseNotify) Header() http.Header {
+ if r.written {
+ return r.responseWriter.Header()
+ }
+ return r.headers
+}
+
+func (r *responseWriterWithoutCloseNotify) Write(buf []byte) (int, error) {
+ if r.ShouldRetry() {
+ return len(buf), nil
+ }
+ return r.responseWriter.Write(buf)
+}
+
+func (r *responseWriterWithoutCloseNotify) WriteHeader(code int) {
+ if r.ShouldRetry() && code == http.StatusServiceUnavailable {
+ // We get a 503 HTTP Status Code when there is no backend server in the pool
+ // to which the request could be sent. Also, note that r.ShouldRetry()
+ // will never return true in case there was a connection established to
+ // the backend server and so we can be sure that the 503 was produced
+ // inside Traefik already and we don't have to retry in this cases.
+ r.DisableRetries()
+ }
+
+ if r.ShouldRetry() {
+ return
+ }
+
+ // In that case retry case is set to false which means we at least managed
+ // to write headers to the backend : we are not going to perform any further retry.
+ // So it is now safe to alter current response headers with headers collected during
+ // the latest try before writing headers to client.
+ headers := r.responseWriter.Header()
+ for header, value := range r.headers {
+ headers[header] = value
+ }
+
+ r.responseWriter.WriteHeader(code)
+ r.written = true
+}
+
+func (r *responseWriterWithoutCloseNotify) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ hijacker, ok := r.responseWriter.(http.Hijacker)
+ if !ok {
+ return nil, nil, fmt.Errorf("%T is not a http.Hijacker", r.responseWriter)
+ }
+ return hijacker.Hijack()
+}
+
+func (r *responseWriterWithoutCloseNotify) Flush() {
+ if flusher, ok := r.responseWriter.(http.Flusher); ok {
+ flusher.Flush()
+ }
+}
+
+type responseWriterWithCloseNotify struct {
+ *responseWriterWithoutCloseNotify
+}
+
+func (r *responseWriterWithCloseNotify) CloseNotify() <-chan bool {
+ return r.responseWriter.(http.CloseNotifier).CloseNotify()
+}
+
+/*
+ * Test case reference
+ * cf. https://github.com/gocolly/colly/blob/b1a8ed2f18144f4b70abcfc18a5e58c68a062389/http_trace.go
+ */
+
+// HTTPTrace provides a datastructure for storing an http trace.
+type HTTPTrace struct {
+ start, connect time.Time
+ ConnectDuration time.Duration
+ FirstByteDuration time.Duration
+}
+
+// trace returns a httptrace.ClientTrace object to be used with an http
+// request via httptrace.WithClientTrace() that fills in the HttpTrace.
+func (ht *HTTPTrace) trace() *httptrace.ClientTrace {
+ trace := &httptrace.ClientTrace{
+ ConnectStart: func(network, addr string) { ht.connect = time.Now() },
+ ConnectDone: func(network, addr string, err error) {
+ ht.ConnectDuration = time.Since(ht.connect)
+ },
+
+ GetConn: func(hostPort string) { ht.start = time.Now() },
+ GotFirstResponseByte: func() {
+ ht.FirstByteDuration = time.Since(ht.start)
+ },
+ }
+ return trace
+}
+
+// WithTrace returns the given HTTP Request with this HTTPTrace added to its
+// context.
+func (ht *HTTPTrace) WithTrace(req *http.Request) *http.Request {
+ // ok: dynamic-httptrace-clienttrace
+ return req.WithContext(httptrace.WithClientTrace(req.Context(), ht.trace()))
+}
+
+/*
+ * Test case reference
+ * cf. https://github.com/mehrdadrad/mylg//blob/616fd5309bb143d3f52ef866b2ffe12135f0dd4e/http/ping/ping.go
+ */
+
+// Ping tries to ping a web server through http
+func (p *Ping) Ping() (Result, error) {
+ var (
+ r Result
+ sTime time.Time
+ resp *http.Response
+ req *http.Request
+ err error
+ )
+
+ client := &http.Client{
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ // Don't follow redirects
+ return http.ErrUseLastResponse
+ },
+ Timeout: p.timeout,
+ Transport: p.transport,
+ }
+
+ sTime = time.Now()
+
+ if p.method == "POST" {
+ r.Size = len(p.buf)
+ reader := strings.NewReader(p.buf)
+ req, err = http.NewRequest(p.method, p.url, reader)
+ } else {
+ req, err = http.NewRequest(p.method, p.url, nil)
+ }
+
+ if err != nil {
+ return r, err
+ }
+
+ // customized header
+ req.Header.Add("User-Agent", p.uAgent)
+ // context, tracert
+ if p.tracerEnabled && !p.quiet {
+ // ok: dynamic-httptrace-clienttrace
+ req = req.WithContext(httptrace.WithClientTrace(req.Context(), tracer(&r)))
+ }
+ resp, err = client.Do(req)
+
+ if err != nil {
+ return r, err
+ }
+ defer resp.Body.Close()
+
+ r.TotalTime = time.Since(sTime).Seconds()
+
+ if p.method == "GET" {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return r, err
+ }
+ r.Size = len(body)
+ } else {
+ io.Copy(ioutil.Discard, resp.Body)
+ }
+
+ r.StatusCode = resp.StatusCode
+ r.Proto = resp.Proto
+ return r, nil
+}
+
+func tracer(r *Result) *httptrace.ClientTrace {
+ var (
+ begin = time.Now()
+ elapsed time.Duration
+ )
+
+ return &httptrace.ClientTrace{
+ ConnectDone: func(network, addr string, err error) {
+ elapsed = time.Since(begin)
+ begin = time.Now()
+ r.Trace.ConnectionTime = elapsed.Seconds() * 1e3
+ },
+ GotFirstResponseByte: func() {
+ elapsed = time.Since(begin)
+ begin = time.Now()
+ r.Trace.TimeToFirstByte = elapsed.Seconds() * 1e3
+ },
+ }
+}
diff --git a/go/lang/security/audit/net/dynamic-httptrace-clienttrace.go b/go/lang/security/audit/net/dynamic-httptrace-clienttrace.go
new file mode 100644
index 00000000..bd20b33e
--- /dev/null
+++ b/go/lang/security/audit/net/dynamic-httptrace-clienttrace.go
@@ -0,0 +1,11 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httptrace"
+)
+
+func WithTrace(req *http.Request, trace *httptrace.ClientTrace) *http.Request {
+ // ruleid: dynamic-httptrace-clienttrace
+ return req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
+}
diff --git a/go/lang/security/audit/net/dynamic-httptrace-clienttrace.yaml b/go/lang/security/audit/net/dynamic-httptrace-clienttrace.yaml
new file mode 100644
index 00000000..36901286
--- /dev/null
+++ b/go/lang/security/audit/net/dynamic-httptrace-clienttrace.yaml
@@ -0,0 +1,38 @@
+rules:
+- id: dynamic-httptrace-clienttrace
+ message: >-
+ Detected a potentially dynamic ClientTrace. This occurred because semgrep could
+ not
+ find a static definition for '$TRACE'. Dynamic ClientTraces are dangerous because
+ they deserialize function code to run when certain Request events occur, which
+ could lead
+ to code being run without your knowledge. Ensure that your ClientTrace is statically
+ defined.
+ metadata:
+ cwe:
+ - 'CWE-913: Improper Control of Dynamically-Managed Code Resources'
+ owasp:
+ - A01:2021 - Broken Access Control
+ references:
+ - https://github.com/returntocorp/semgrep-rules/issues/518
+ # Detects when a static ClientTrace is not defined in the same file as
+ # WithClientTrace. Not a perfect detection, but sufficiently works in a
+ # scan of ~1k repos: https://dev.massive.ret2.co/triager/filter/1007
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: LOW
+ patterns:
+ - pattern-not-inside: |
+ package $PACKAGE
+ ...
+ &httptrace.ClientTrace { ... }
+ ...
+ - pattern: httptrace.WithClientTrace($ANY, $TRACE)
+ severity: WARNING
+ languages:
+ - go
diff --git a/go/lang/security/audit/net/formatted-template-string.go b/go/lang/security/audit/net/formatted-template-string.go
new file mode 100644
index 00000000..f7a35d88
--- /dev/null
+++ b/go/lang/security/audit/net/formatted-template-string.go
@@ -0,0 +1,75 @@
+package main
+
+import (
+ "fmt"
+ "html/template"
+ "net/http"
+ "strconv"
+)
+
+func Fine(r *http.Request) template.HTML {
+ // ok: formatted-template-string
+ return template.HTML("Hello, world
")
+}
+
+func AlsoFine(r *http.Request) template.HTML {
+ // ok: formatted-template-string
+ return template.HTML("" + "Hello, world
")
+}
+
+func Concat(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: formatted-template-string
+ tmpl := "" + customerId + "
"
+
+ return template.HTML(tmpl)
+}
+
+func ConcatBranch(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ doIt, err := strconv.ParseBool(r.URL.Query().Get("do"))
+ if err != nil {
+ return template.HTML("")
+ }
+ var tmpl string
+ if doIt {
+ // todo: formatted-template-string
+ tmpl = "" + customerId + "
"
+ } else {
+ tmpl = ""
+ }
+
+ return template.HTML(tmpl)
+}
+
+func ConcatInline(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: formatted-template-string
+ return template.HTML("" + customerId + "
")
+}
+
+func ConcatInlineOneside(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: formatted-template-string
+ return template.HTML("" + customerId)
+}
+
+func Formatted(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: formatted-template-string
+ tmpl, err := fmt.Printf("%s
", customerId)
+ if err != nil {
+ return template.HTML("")
+ }
+ return template.HTML(tmpl)
+}
+
+func FormattedInline(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: formatted-template-string
+ return template.HTML(fmt.Sprintf("%s
", customerId))
+}
+
+func main() {}
diff --git a/go/lang/security/audit/net/formatted-template-string.yaml b/go/lang/security/audit/net/formatted-template-string.yaml
new file mode 100644
index 00000000..1a804a24
--- /dev/null
+++ b/go/lang/security/audit/net/formatted-template-string.yaml
@@ -0,0 +1,55 @@
+rules:
+- id: formatted-template-string
+ message: >-
+ Found a formatted template string passed to 'template.HTML()'. 'template.HTML()' does not escape
+ contents. Be absolutely sure there is no user-controlled data in this template. If user data can
+ reach this template, you may have a XSS vulnerability.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/pkg/html/template/#HTML
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-not: template.HTML("..." + "...")
+ - pattern-either:
+ - pattern: template.HTML($T + $X, ...)
+ - pattern: template.HTML(fmt.$P("...", ...), ...)
+ - pattern: |
+ $T = "..."
+ ...
+ $T = $FXN(..., $T, ...)
+ ...
+ template.HTML($T, ...)
+ - pattern: |
+ $T = fmt.$P("...", ...)
+ ...
+ template.HTML($T, ...)
+ - pattern: |
+ $T, $ERR = fmt.$P("...", ...)
+ ...
+ template.HTML($T, ...)
+ - pattern: |
+ $T = $X + $Y
+ ...
+ template.HTML($T, ...)
+ - pattern: |-
+ $T = "..."
+ ...
+ $OTHER, $ERR = fmt.$P(..., $T, ...)
+ ...
+ template.HTML($OTHER, ...)
diff --git a/go/lang/security/audit/net/fs-directory-listing.go b/go/lang/security/audit/net/fs-directory-listing.go
new file mode 100644
index 00000000..98ec97d5
--- /dev/null
+++ b/go/lang/security/audit/net/fs-directory-listing.go
@@ -0,0 +1,49 @@
+package main
+
+import (
+ "log"
+ "net/http"
+)
+
+func dirListing1() {
+ fs := http.FileServer(http.Dir(""))
+ //ruleid: fs-directory-listing
+ log.Fatal(http.ListenAndServe(":9000", fs))
+}
+
+func dirListing2() {
+ fs := http.FileServer(http.Dir(""))
+ certFile := "/path/tp/my/cert"
+ keyFile := "/path/to/my/key"
+ //ruleid: fs-directory-listing
+ log.Fatal(http.ListenAndServeTLS(":9000", certFile, keyFile, fs))
+}
+
+func dirListing3() {
+ fs := http.FileServer(http.Dir(""))
+ //ruleid: fs-directory-listing
+ http.Handle("/myroute", fs)
+}
+
+func dirListing4() {
+ //ruleid: fs-directory-listing
+ http.Handle("/myroute", http.FileServer(http.Dir("")))
+}
+
+func noDirListing1() {
+ h1 := func(w http.ResponseWriter, _ *http.Request) {
+ w.Write([]byte("Hello!
"))
+ }
+ //ok: fs-directory-listing
+ http.HandleFunc("/myroute", h1)
+}
+
+func noDirListing2() {
+ h1 := func(w http.ResponseWriter, _ *http.Request) {
+ w.Write([]byte("Home page
"))
+ }
+ mux := http.NewServeMux()
+ mux.HandleFunc("/", h1)
+ //ok: fs-directory-listing
+ log.Fatal(http.ListenAndServe(":9000", mux))
+}
diff --git a/go/lang/security/audit/net/fs-directory-listing.yaml b/go/lang/security/audit/net/fs-directory-listing.yaml
new file mode 100644
index 00000000..8cd4e68a
--- /dev/null
+++ b/go/lang/security/audit/net/fs-directory-listing.yaml
@@ -0,0 +1,48 @@
+rules:
+- id: fs-directory-listing
+ message: >-
+ Detected usage of 'http.FileServer' as handler: this allows directory listing
+ and an attacker could navigate through directories looking for sensitive
+ files. Be sure to disable directory listing or restrict access to specific
+ directories/files.
+ severity: WARNING
+ languages:
+ - go
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern-inside: |
+ $FS := http.FileServer(...)
+ ...
+ - pattern-either:
+ - pattern: |
+ http.ListenAndServe(..., $FS)
+ - pattern: |
+ http.ListenAndServeTLS(..., $FS)
+ - pattern: |
+ http.Handle(..., $FS)
+ - pattern: |
+ http.HandleFunc(..., $FS)
+ - patterns:
+ - pattern: |
+ http.$FN(..., http.FileServer(...))
+ - metavariable-regex:
+ metavariable: $FN
+ regex: (ListenAndServe|ListenAndServeTLS|Handle|HandleFunc)
+ metadata:
+ category: security
+ cwe:
+ - 'CWE-548: Exposure of Information Through Directory Listing'
+ owasp:
+ - A06:2017 - Security Misconfiguration
+ - A01:2021 - Broken Access Control
+ references:
+ - https://github.com/OWASP/Go-SCP
+ - https://cwe.mitre.org/data/definitions/548.html
+ confidence: MEDIUM
+ technology:
+ - go
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
diff --git a/go/lang/security/audit/net/pprof.go b/go/lang/security/audit/net/pprof.go
new file mode 100644
index 00000000..45f193d4
--- /dev/null
+++ b/go/lang/security/audit/net/pprof.go
@@ -0,0 +1,43 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ _ "net/http/pprof"
+)
+
+func ok() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello World!")
+ })
+ // ok: pprof-debug-exposure
+ log.Fatal(http.ListenAndServe("localhost:8080", nil))
+}
+
+func ok2() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello World!")
+ })
+ // ok: pprof-debug-exposure
+ log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil))
+}
+
+func ok3() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello World!")
+ })
+
+ mux := http.NewServeMux()
+ // ok: pprof-debug-exposure
+ log.Fatal(http.ListenAndServe(":8080", mux))
+}
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello World!")
+ })
+ // ruleid: pprof-debug-exposure
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
diff --git a/go/lang/security/audit/net/pprof.yaml b/go/lang/security/audit/net/pprof.yaml
new file mode 100644
index 00000000..be595d45
--- /dev/null
+++ b/go/lang/security/audit/net/pprof.yaml
@@ -0,0 +1,40 @@
+rules:
+- id: pprof-debug-exposure
+ metadata:
+ cwe:
+ - 'CWE-489: Active Debug Code'
+ owasp: 'A06:2017 - Security Misconfiguration'
+ source-rule-url: https://github.com/securego/gosec#available-rules
+ references:
+ - https://www.farsightsecurity.com/blog/txt-record/go-remote-profiling-20161028/
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ message: >-
+ The profiling 'pprof' endpoint is automatically exposed on /debug/pprof.
+ This could leak information about the server.
+ Instead, use `import "net/http/pprof"`. See
+ https://www.farsightsecurity.com/blog/txt-record/go-remote-profiling-20161028/
+ for more information and mitigation.
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-inside: |
+ import _ "net/http/pprof"
+ ...
+ - pattern-inside: |
+ func $ANY(...) {
+ ...
+ }
+ - pattern-not-inside: |
+ $MUX = http.NewServeMux(...)
+ ...
+ http.ListenAndServe($ADDR, $MUX)
+ - pattern-not: http.ListenAndServe("=~/^localhost.*/", ...)
+ - pattern-not: http.ListenAndServe("=~/^127[.]0[.]0[.]1.*/", ...)
+ - pattern: http.ListenAndServe(...)
diff --git a/go/lang/security/audit/net/pprof_good.go b/go/lang/security/audit/net/pprof_good.go
new file mode 100644
index 00000000..748b637e
--- /dev/null
+++ b/go/lang/security/audit/net/pprof_good.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ // ok: pprof-debug-exposure
+ "net/http/pprof"
+)
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello World!")
+ })
+ pprof.StartCPUProfile()
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
diff --git a/go/lang/security/audit/net/pprof_good2.go b/go/lang/security/audit/net/pprof_good2.go
new file mode 100644
index 00000000..7c391df2
--- /dev/null
+++ b/go/lang/security/audit/net/pprof_good2.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ // OK
+ _ "net/http/pprof"
+)
+
+func main() {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintf(w, "Hello World!")
+ })
+ log.Fatal(http.ListenAndServe("localhost:8080", nil))
+}
diff --git a/go/lang/security/audit/net/unescaped-data-in-htmlattr.go b/go/lang/security/audit/net/unescaped-data-in-htmlattr.go
new file mode 100644
index 00000000..4e9bb902
--- /dev/null
+++ b/go/lang/security/audit/net/unescaped-data-in-htmlattr.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "html/template"
+ "net/http"
+)
+
+const tmpl = ""
+
+func Concat(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: unescaped-data-in-htmlattr
+ tmpl := "" + customerId + "
"
+ return template.HTMLAttr(tmpl)
+}
diff --git a/go/lang/security/audit/net/unescaped-data-in-htmlattr.yaml b/go/lang/security/audit/net/unescaped-data-in-htmlattr.yaml
new file mode 100644
index 00000000..bb87d089
--- /dev/null
+++ b/go/lang/security/audit/net/unescaped-data-in-htmlattr.yaml
@@ -0,0 +1,53 @@
+rules:
+- id: unescaped-data-in-htmlattr
+ message: >-
+ Found a formatted template string passed to 'template.
+ HTMLAttr()'. 'template.HTMLAttr()' does not escape contents. Be absolutely sure there is no user-controlled
+ data in this template or validate and sanitize the data before passing it into the template.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/pkg/html/template/#HTMLAttr
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages: [go]
+ severity: WARNING
+ pattern-either:
+ - pattern: template.HTMLAttr($T + $X, ...)
+ - pattern: template.HTMLAttr(fmt.$P("...", ...), ...)
+ - pattern: |
+ $T = "..."
+ ...
+ $T = $FXN(..., $T, ...)
+ ...
+ template.HTMLAttr($T, ...)
+ - pattern: |
+ $T = fmt.$P("...", ...)
+ ...
+ template.HTMLAttr($T, ...)
+ - pattern: |
+ $T, $ERR = fmt.$P("...", ...)
+ ...
+ template.HTMLAttr($T, ...)
+ - pattern: |
+ $T = $X + $Y
+ ...
+ template.HTMLAttr($T, ...)
+ - pattern: |-
+ $T = "..."
+ ...
+ $OTHER, $ERR = fmt.$P(..., $T, ...)
+ ...
+ template.HTMLAttr($OTHER, ...)
diff --git a/go/lang/security/audit/net/unescaped-data-in-js.go b/go/lang/security/audit/net/unescaped-data-in-js.go
new file mode 100644
index 00000000..70883c47
--- /dev/null
+++ b/go/lang/security/audit/net/unescaped-data-in-js.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "html/template"
+ "net/http"
+)
+
+const tmpl = ""
+
+func Concat(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: unescaped-data-in-js
+ tmpl := "" + customerId + "
"
+ return template.JS(tmpl)
+}
diff --git a/go/lang/security/audit/net/unescaped-data-in-js.yaml b/go/lang/security/audit/net/unescaped-data-in-js.yaml
new file mode 100644
index 00000000..ff130a11
--- /dev/null
+++ b/go/lang/security/audit/net/unescaped-data-in-js.yaml
@@ -0,0 +1,53 @@
+rules:
+- id: unescaped-data-in-js
+ message: >-
+ Found a formatted template string passed to 'template.JS()'.
+ 'template.JS()' does not escape contents. Be absolutely sure
+ there is no user-controlled data in this template.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/pkg/html/template/#JS
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages: [go]
+ severity: WARNING
+ pattern-either:
+ - pattern: template.JS($T + $X, ...)
+ - pattern: template.JS(fmt.$P("...", ...), ...)
+ - pattern: |
+ $T = "..."
+ ...
+ $T = $FXN(..., $T, ...)
+ ...
+ template.JS($T, ...)
+ - pattern: |
+ $T = fmt.$P("...", ...)
+ ...
+ template.JS($T, ...)
+ - pattern: |
+ $T, $ERR = fmt.$P("...", ...)
+ ...
+ template.JS($T, ...)
+ - pattern: |
+ $T = $X + $Y
+ ...
+ template.JS($T, ...)
+ - pattern: |
+ $T = "..."
+ ...
+ $OTHER, $ERR = fmt.$P(..., $T, ...)
+ ...
+ template.JS($OTHER, ...)
diff --git a/go/lang/security/audit/net/unescaped-data-in-url.go b/go/lang/security/audit/net/unescaped-data-in-url.go
new file mode 100644
index 00000000..b6df704c
--- /dev/null
+++ b/go/lang/security/audit/net/unescaped-data-in-url.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "html/template"
+ "net/http"
+)
+
+const tmpl = ""
+
+func Concat(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: unescaped-data-in-url
+ tmpl := "" + customerId + "
"
+
+ return template.URL(tmpl)
+}
diff --git a/go/lang/security/audit/net/unescaped-data-in-url.yaml b/go/lang/security/audit/net/unescaped-data-in-url.yaml
new file mode 100644
index 00000000..c88dd42b
--- /dev/null
+++ b/go/lang/security/audit/net/unescaped-data-in-url.yaml
@@ -0,0 +1,54 @@
+rules:
+- id: unescaped-data-in-url
+ message: >-
+ Found a formatted template string passed to 'template.URL()'.
+ 'template.URL()' does not escape contents, and this could result in XSS (cross-site scripting) and
+ therefore confidential data being stolen. Sanitize data coming into this function or make sure that no
+ user-controlled input is coming into the function.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/pkg/html/template/#URL
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages: [go]
+ severity: WARNING
+ pattern-either:
+ - pattern: template.URL($T + $X, ...)
+ - pattern: template.URL(fmt.$P("...", ...), ...)
+ - pattern: |
+ $T = "..."
+ ...
+ $T = $FXN(..., $T, ...)
+ ...
+ template.URL($T, ...)
+ - pattern: |
+ $T = fmt.$P("...", ...)
+ ...
+ template.URL($T, ...)
+ - pattern: |
+ $T, $ERR = fmt.$P("...", ...)
+ ...
+ template.URL($T, ...)
+ - pattern: |
+ $T = $X + $Y
+ ...
+ template.URL($T, ...)
+ - pattern: |-
+ $T = "..."
+ ...
+ $OTHER, $ERR = fmt.$P(..., $T, ...)
+ ...
+ template.URL($OTHER, ...)
diff --git a/go/lang/security/audit/net/use-tls.fixed.go b/go/lang/security/audit/net/use-tls.fixed.go
new file mode 100644
index 00000000..20bc2da5
--- /dev/null
+++ b/go/lang/security/audit/net/use-tls.fixed.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+ "net/http"
+ "fmt"
+)
+
+func Handler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.write([]byte("Hello, world!"))
+}
+
+func main() {
+ http.HandleFunc("/index", Handler)
+ // ruleid: use-tls
+ http.ListenAndServeTLS(":80", certFile, keyFile, nil)
+}
diff --git a/go/lang/security/audit/net/use-tls.go b/go/lang/security/audit/net/use-tls.go
new file mode 100644
index 00000000..62b89d43
--- /dev/null
+++ b/go/lang/security/audit/net/use-tls.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+ "net/http"
+ "fmt"
+)
+
+func Handler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ w.write([]byte("Hello, world!"))
+}
+
+func main() {
+ http.HandleFunc("/index", Handler)
+ // ruleid: use-tls
+ http.ListenAndServe(":80", nil)
+}
diff --git a/go/lang/security/audit/net/use-tls.yaml b/go/lang/security/audit/net/use-tls.yaml
new file mode 100644
index 00000000..902b27c4
--- /dev/null
+++ b/go/lang/security/audit/net/use-tls.yaml
@@ -0,0 +1,25 @@
+rules:
+- id: use-tls
+ pattern: http.ListenAndServe($ADDR, $HANDLER)
+ fix: http.ListenAndServeTLS($ADDR, certFile, keyFile, $HANDLER)
+ metadata:
+ cwe:
+ - 'CWE-319: Cleartext Transmission of Sensitive Information'
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ references:
+ - https://golang.org/pkg/net/http/#ListenAndServeTLS
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ message: >-
+ Found an HTTP server without TLS. Use 'http.ListenAndServeTLS' instead.
+ See https://golang.org/pkg/net/http/#ListenAndServeTLS for more information.
+ languages: [go]
+ severity: WARNING
diff --git a/go/lang/security/audit/net/wip-xss-using-responsewriter-and-printf.go b/go/lang/security/audit/net/wip-xss-using-responsewriter-and-printf.go
new file mode 100644
index 00000000..dd4636bb
--- /dev/null
+++ b/go/lang/security/audit/net/wip-xss-using-responsewriter-and-printf.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func getMovieQuote() map[string]string {
+ m := make(map[string]string)
+ m["quote"] = "I'll be back."
+ m["movie"] = "The Terminator"
+ m["year"] = "1984"
+
+ return m
+}
+
+func indexPage(w http.ResponseWriter, r *http.Request) {
+ const tme = ``
+
+ const template = `
+
+
+ Random Movie Quotes
+ %s
+ ~%s, %s
+
+ `
+
+ quote := getMovieQuote()
+
+ quoteText := quote["quote"]
+ movie := quote["movie"]
+ year := quote["year"]
+
+ w.WriteHeader(http.StatusAccepted)
+ w.Write([]byte(fmt.Sprintf(template, quoteText, movie, year)))
+}
+
+func errorPage(w http.ResponseWriter, r *http.Request) {
+ // ruleid: wip-xss-using-responsewriter-and-printf
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ const template = `
+
+
+ error; page not found. go back
+
+ `
+
+ w.WriteHeader(http.StatusAccepted)
+ w.Write([]byte(fmt.Sprintf(template, url)))
+}
+
+func main() {
+ http.HandleFunc("/", indexPage)
+ http.HandleFunc("/error", errorPage)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/go/lang/security/audit/net/wip-xss-using-responsewriter-and-printf.yaml b/go/lang/security/audit/net/wip-xss-using-responsewriter-and-printf.yaml
new file mode 100644
index 00000000..6d9b3b06
--- /dev/null
+++ b/go/lang/security/audit/net/wip-xss-using-responsewriter-and-printf.yaml
@@ -0,0 +1,72 @@
+rules:
+- id: wip-xss-using-responsewriter-and-printf
+ patterns:
+ - pattern-inside: |
+ func $FUNC(..., $W http.ResponseWriter, ...) {
+ ...
+ var $TEMPLATE = "..."
+ ...
+ $W.Write([]byte(fmt.$PRINTF($TEMPLATE, ...)), ...)
+ ...
+ }
+ - pattern-either:
+ - pattern: |
+ $PARAMS = r.URL.Query()
+ ...
+ $DATA, $ERR := $PARAMS[...]
+ ...
+ $INTERM = $ANYTHING(..., $DATA, ...)
+ ...
+ $W.Write([]byte(fmt.$PRINTF(..., $INTERM, ...)))
+ - pattern: |
+ $PARAMS = r.URL.Query()
+ ...
+ $DATA, $ERR := $PARAMS[...]
+ ...
+ $INTERM = $DATA[...]
+ ...
+ $W.Write([]byte(fmt.$PRINTF(..., $INTERM, ...)))
+ - pattern: |
+ $DATA, $ERR := r.URL.Query()[...]
+ ...
+ $INTERM = $DATA[...]
+ ...
+ $W.Write([]byte(fmt.$PRINTF(..., $INTERM, ...)))
+ - pattern: |
+ $DATA, $ERR := r.URL.Query()[...]
+ ...
+ $INTERM = $ANYTHING(..., $DATA, ...)
+ ...
+ $W.Write([]byte(fmt.$PRINTF(..., $INTERM, ...)))
+ - pattern: |
+ $PARAMS = r.URL.Query()
+ ...
+ $DATA, $ERR := $PARAMS[...]
+ ...
+ $W.Write([]byte(fmt.$PRINTF(..., $DATA, ...)))
+ message: >-
+ Found data going from url query parameters into formatted data written to ResponseWriter.
+ This could be XSS and should not be done. If you must do this, ensure your data
+ is
+ sanitized or escaped.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: MEDIUM
+ severity: WARNING
+ languages:
+ - go
diff --git a/go/lang/security/audit/reflect-makefunc.go b/go/lang/security/audit/reflect-makefunc.go
new file mode 100644
index 00000000..6b73c2fa
--- /dev/null
+++ b/go/lang/security/audit/reflect-makefunc.go
@@ -0,0 +1,831 @@
+/*
+* Test case reference:
+* https://github.com/robertkrimen/otto//blob/c382bd3c16ff2fef9b5fe0dd8bf4c4ec6bfe62c1/runtime.go#L489
+ */
+
+package main
+
+import (
+ "encoding"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math"
+ "path"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/robertkrimen/otto/ast"
+ "github.com/robertkrimen/otto/parser"
+)
+
+type _global struct {
+ Object *_object // Object( ... ), new Object( ... ) - 1 (length)
+ Function *_object // Function( ... ), new Function( ... ) - 1
+ Array *_object // Array( ... ), new Array( ... ) - 1
+ String *_object // String( ... ), new String( ... ) - 1
+ Boolean *_object // Boolean( ... ), new Boolean( ... ) - 1
+ Number *_object // Number( ... ), new Number( ... ) - 1
+ Math *_object
+ Date *_object // Date( ... ), new Date( ... ) - 7
+ RegExp *_object // RegExp( ... ), new RegExp( ... ) - 2
+ Error *_object // Error( ... ), new Error( ... ) - 1
+ EvalError *_object
+ TypeError *_object
+ RangeError *_object
+ ReferenceError *_object
+ SyntaxError *_object
+ URIError *_object
+ JSON *_object
+
+ ObjectPrototype *_object // Object.prototype
+ FunctionPrototype *_object // Function.prototype
+ ArrayPrototype *_object // Array.prototype
+ StringPrototype *_object // String.prototype
+ BooleanPrototype *_object // Boolean.prototype
+ NumberPrototype *_object // Number.prototype
+ DatePrototype *_object // Date.prototype
+ RegExpPrototype *_object // RegExp.prototype
+ ErrorPrototype *_object // Error.prototype
+ EvalErrorPrototype *_object
+ TypeErrorPrototype *_object
+ RangeErrorPrototype *_object
+ ReferenceErrorPrototype *_object
+ SyntaxErrorPrototype *_object
+ URIErrorPrototype *_object
+}
+
+type _runtime struct {
+ global _global
+ globalObject *_object
+ globalStash *_objectStash
+ scope *_scope
+ otto *Otto
+ eval *_object // The builtin eval, for determine indirect versus direct invocation
+ debugger func(*Otto)
+ random func() float64
+ stackLimit int
+ traceLimit int
+
+ labels []string // FIXME
+ lck sync.Mutex
+}
+
+func (self *_runtime) enterScope(scope *_scope) {
+ scope.outer = self.scope
+ if self.scope != nil {
+ if self.stackLimit != 0 && self.scope.depth+1 >= self.stackLimit {
+ panic(self.panicRangeError("Maximum call stack size exceeded"))
+ }
+
+ scope.depth = self.scope.depth + 1
+ }
+
+ self.scope = scope
+}
+
+func (self *_runtime) leaveScope() {
+ self.scope = self.scope.outer
+}
+
+// FIXME This is used in two places (cloning)
+func (self *_runtime) enterGlobalScope() {
+ self.enterScope(newScope(self.globalStash, self.globalStash, self.globalObject))
+}
+
+func (self *_runtime) enterFunctionScope(outer _stash, this Value) *_fnStash {
+ if outer == nil {
+ outer = self.globalStash
+ }
+ stash := self.newFunctionStash(outer)
+ var thisObject *_object
+ switch this.kind {
+ case valueUndefined, valueNull:
+ thisObject = self.globalObject
+ default:
+ thisObject = self.toObject(this)
+ }
+ self.enterScope(newScope(stash, stash, thisObject))
+ return stash
+}
+
+func (self *_runtime) putValue(reference _reference, value Value) {
+ name := reference.putValue(value)
+ if name != "" {
+ // Why? -- If reference.base == nil
+ // strict = false
+ self.globalObject.defineProperty(name, value, 0111, false)
+ }
+}
+
+func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exception bool) {
+ // resultValue = The value of the block (e.g. the last statement)
+ // throw = Something was thrown
+ // throwValue = The value of what was thrown
+ // other = Something that changes flow (return, break, continue) that is not a throw
+ // Otherwise, some sort of unknown panic happened, we'll just propagate it
+ defer func() {
+ if caught := recover(); caught != nil {
+ if exception, ok := caught.(*_exception); ok {
+ caught = exception.eject()
+ }
+ switch caught := caught.(type) {
+ case _error:
+ exception = true
+ tryValue = toValue_object(self.newError(caught.name, caught.messageValue(), 0))
+ case Value:
+ exception = true
+ tryValue = caught
+ default:
+ panic(caught)
+ }
+ }
+ }()
+
+ tryValue = inner()
+ return
+}
+
+// toObject
+
+func (self *_runtime) toObject(value Value) *_object {
+ switch value.kind {
+ case valueEmpty, valueUndefined, valueNull:
+ panic(self.panicTypeError())
+ case valueBoolean:
+ return self.newBoolean(value)
+ case valueString:
+ return self.newString(value)
+ case valueNumber:
+ return self.newNumber(value)
+ case valueObject:
+ return value._object()
+ }
+ panic(self.panicTypeError())
+}
+
+func (self *_runtime) objectCoerce(value Value) (*_object, error) {
+ switch value.kind {
+ case valueUndefined:
+ return nil, errors.New("undefined")
+ case valueNull:
+ return nil, errors.New("null")
+ case valueBoolean:
+ return self.newBoolean(value), nil
+ case valueString:
+ return self.newString(value), nil
+ case valueNumber:
+ return self.newNumber(value), nil
+ case valueObject:
+ return value._object(), nil
+ }
+ panic(self.panicTypeError())
+}
+
+func checkObjectCoercible(rt *_runtime, value Value) {
+ isObject, mustCoerce := testObjectCoercible(value)
+ if !isObject && !mustCoerce {
+ panic(rt.panicTypeError())
+ }
+}
+
+// testObjectCoercible
+
+func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
+ switch value.kind {
+ case valueReference, valueEmpty, valueNull, valueUndefined:
+ return false, false
+ case valueNumber, valueString, valueBoolean:
+ return false, true
+ case valueObject:
+ return true, false
+ default:
+ panic("this should never happen")
+ }
+}
+
+func (self *_runtime) safeToValue(value interface{}) (Value, error) {
+ result := Value{}
+ err := catchPanic(func() {
+ result = self.toValue(value)
+ })
+ return result, err
+}
+
+// convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics.
+// This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily.
+func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
+ val := reflect.ValueOf(v.export())
+
+ if val.Kind() == t.Kind() {
+ return val
+ }
+
+ if val.Kind() == reflect.Interface {
+ val = reflect.ValueOf(val.Interface())
+ }
+
+ switch val.Kind() {
+ case reflect.Float32, reflect.Float64:
+ f64 := val.Float()
+ switch t.Kind() {
+ case reflect.Float64:
+ return reflect.ValueOf(f64)
+ case reflect.Float32:
+ if reflect.Zero(t).OverflowFloat(f64) {
+ panic(self.panicRangeError("converting float64 to float32 would overflow"))
+ }
+
+ return val.Convert(t)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ i64 := int64(f64)
+ if float64(i64) != f64 {
+ panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t)))
+ }
+
+ // The float represents an integer
+ val = reflect.ValueOf(i64)
+ default:
+ panic(self.panicTypeError(fmt.Sprintf("cannot convert %v to %v", val.Type(), t)))
+ }
+ }
+
+ switch val.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ i64 := val.Int()
+ switch t.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if reflect.Zero(t).OverflowInt(i64) {
+ panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
+ }
+ return val.Convert(t)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if i64 < 0 {
+ panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t)))
+ }
+ if reflect.Zero(t).OverflowUint(uint64(i64)) {
+ panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
+ }
+ return val.Convert(t)
+ case reflect.Float32, reflect.Float64:
+ return val.Convert(t)
+ }
+
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ u64 := val.Uint()
+ switch t.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) {
+ panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
+ }
+ return val.Convert(t)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if reflect.Zero(t).OverflowUint(u64) {
+ panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
+ }
+ return val.Convert(t)
+ case reflect.Float32, reflect.Float64:
+ return val.Convert(t)
+ }
+ }
+
+ panic(self.panicTypeError(fmt.Sprintf("unsupported type %v -> %v for numeric conversion", val.Type(), t)))
+}
+
+func fieldIndexByName(t reflect.Type, name string) []int {
+ for i := 0; i < t.NumField(); i++ {
+ f := t.Field(i)
+
+ if !validGoStructName(f.Name) {
+ continue
+ }
+
+ if f.Anonymous {
+ if a := fieldIndexByName(f.Type, name); a != nil {
+ return append([]int{i}, a...)
+ }
+ }
+
+ if a := strings.SplitN(f.Tag.Get("json"), ",", 2); a[0] != "" {
+ if a[0] == "-" {
+ continue
+ }
+
+ if a[0] == name {
+ return []int{i}
+ }
+ }
+
+ if f.Name == name {
+ return []int{i}
+ }
+ }
+
+ return nil
+}
+
+var typeOfValue = reflect.TypeOf(Value{})
+var typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{})
+
+// convertCallParameter converts request val to type t if possible.
+// If the conversion fails due to overflow or type miss-match then it panics.
+// If no conversion is known then the original value is returned.
+func (self *_runtime) convertCallParameter(v Value, t reflect.Type) reflect.Value {
+ if t == typeOfValue {
+ return reflect.ValueOf(v)
+ }
+
+ if t == typeOfJSONRawMessage {
+ if d, err := json.Marshal(v.export()); err == nil {
+ return reflect.ValueOf(d)
+ }
+ }
+
+ if v.kind == valueObject {
+ if gso, ok := v._object().value.(*_goStructObject); ok {
+ if gso.value.Type().AssignableTo(t) {
+ // please see TestDynamicFunctionReturningInterface for why this exists
+ if t.Kind() == reflect.Interface && gso.value.Type().ConvertibleTo(t) {
+ return gso.value.Convert(t)
+ } else {
+ return gso.value
+ }
+ }
+ }
+
+ if gao, ok := v._object().value.(*_goArrayObject); ok {
+ if gao.value.Type().AssignableTo(t) {
+ // please see TestDynamicFunctionReturningInterface for why this exists
+ if t.Kind() == reflect.Interface && gao.value.Type().ConvertibleTo(t) {
+ return gao.value.Convert(t)
+ } else {
+ return gao.value
+ }
+ }
+ }
+ }
+
+ if t.Kind() == reflect.Interface {
+ e := v.export()
+ if e == nil {
+ return reflect.Zero(t)
+ }
+ iv := reflect.ValueOf(e)
+ if iv.Type().AssignableTo(t) {
+ return iv
+ }
+ }
+
+ tk := t.Kind()
+
+ if tk == reflect.Ptr {
+ switch v.kind {
+ case valueEmpty, valueNull, valueUndefined:
+ return reflect.Zero(t)
+ default:
+ var vv reflect.Value
+ if err := catchPanic(func() { vv = self.convertCallParameter(v, t.Elem()) }); err == nil {
+ if vv.CanAddr() {
+ return vv.Addr()
+ }
+
+ pv := reflect.New(vv.Type())
+ pv.Elem().Set(vv)
+ return pv
+ }
+ }
+ }
+
+ switch tk {
+ case reflect.Bool:
+ return reflect.ValueOf(v.bool())
+ case reflect.String:
+ switch v.kind {
+ case valueString:
+ return reflect.ValueOf(v.value)
+ case valueNumber:
+ return reflect.ValueOf(fmt.Sprintf("%v", v.value))
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
+ switch v.kind {
+ case valueNumber:
+ return self.convertNumeric(v, t)
+ }
+ case reflect.Slice:
+ if o := v._object(); o != nil {
+ if lv := o.get("length"); lv.IsNumber() {
+ l := lv.number().int64
+
+ s := reflect.MakeSlice(t, int(l), int(l))
+
+ tt := t.Elem()
+
+ if o.class == "Array" {
+ for i := int64(0); i < l; i++ {
+ p, ok := o.property[strconv.FormatInt(i, 10)]
+ if !ok {
+ continue
+ }
+
+ e, ok := p.value.(Value)
+ if !ok {
+ continue
+ }
+
+ ev := self.convertCallParameter(e, tt)
+
+ s.Index(int(i)).Set(ev)
+ }
+ } else if o.class == "GoArray" {
+
+ var gslice bool
+ switch o.value.(type) {
+ case *_goSliceObject:
+ gslice = true
+ case *_goArrayObject:
+ gslice = false
+ }
+
+ for i := int64(0); i < l; i++ {
+ var p *_property
+ if gslice {
+ p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10))
+ } else {
+ p = goArrayGetOwnProperty(o, strconv.FormatInt(i, 10))
+ }
+ if p == nil {
+ continue
+ }
+
+ e, ok := p.value.(Value)
+ if !ok {
+ continue
+ }
+
+ ev := self.convertCallParameter(e, tt)
+
+ s.Index(int(i)).Set(ev)
+ }
+ }
+
+ return s
+ }
+ }
+ case reflect.Map:
+ if o := v._object(); o != nil && t.Key().Kind() == reflect.String {
+ m := reflect.MakeMap(t)
+
+ o.enumerate(false, func(k string) bool {
+ m.SetMapIndex(reflect.ValueOf(k), self.convertCallParameter(o.get(k), t.Elem()))
+ return true
+ })
+
+ return m
+ }
+ case reflect.Func:
+ if t.NumOut() > 1 {
+ panic(self.panicTypeError("converting JavaScript values to Go functions with more than one return value is currently not supported"))
+ }
+
+ if o := v._object(); o != nil && o.class == "Function" {
+ // ruleid: reflect-makefunc
+ return reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value {
+ l := make([]interface{}, len(args))
+ for i, a := range args {
+ if a.CanInterface() {
+ l[i] = a.Interface()
+ }
+ }
+
+ rv, err := v.Call(nullValue, l...)
+ if err != nil {
+ panic(err)
+ }
+
+ if t.NumOut() == 0 {
+ return nil
+ }
+
+ return []reflect.Value{self.convertCallParameter(rv, t.Out(0))}
+ })
+ }
+ case reflect.Struct:
+ if o := v._object(); o != nil && o.class == "Object" {
+ s := reflect.New(t)
+
+ for _, k := range o.propertyOrder {
+ idx := fieldIndexByName(t, k)
+
+ if idx == nil {
+ panic(self.panicTypeError("can't convert object; field %q was supplied but does not exist on target %v", k, t))
+ }
+
+ ss := s
+
+ for _, i := range idx {
+ if ss.Kind() == reflect.Ptr {
+ if ss.IsNil() {
+ if !ss.CanSet() {
+ panic(self.panicTypeError("can't set embedded pointer to unexported struct: %v", ss.Type().Elem()))
+ }
+
+ ss.Set(reflect.New(ss.Type().Elem()))
+ }
+
+ ss = ss.Elem()
+ }
+
+ ss = ss.Field(i)
+ }
+
+ ss.Set(self.convertCallParameter(o.get(k), ss.Type()))
+ }
+
+ return s.Elem()
+ }
+ }
+
+ if tk == reflect.String {
+ if o := v._object(); o != nil && o.hasProperty("toString") {
+ if fn := o.get("toString"); fn.IsFunction() {
+ sv, err := fn.Call(v)
+ if err != nil {
+ panic(err)
+ }
+
+ var r reflect.Value
+ if err := catchPanic(func() { r = self.convertCallParameter(sv, t) }); err == nil {
+ return r
+ }
+ }
+ }
+
+ return reflect.ValueOf(v.String())
+ }
+
+ if v.kind == valueString {
+ var s encoding.TextUnmarshaler
+
+ if reflect.PtrTo(t).Implements(reflect.TypeOf(&s).Elem()) {
+ r := reflect.New(t)
+
+ if err := r.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(v.string())); err != nil {
+ panic(self.panicSyntaxError("can't convert to %s: %s", t.String(), err.Error()))
+ }
+
+ return r.Elem()
+ }
+ }
+
+ s := "OTTO DOES NOT UNDERSTAND THIS TYPE"
+ switch v.kind {
+ case valueBoolean:
+ s = "boolean"
+ case valueNull:
+ s = "null"
+ case valueNumber:
+ s = "number"
+ case valueString:
+ s = "string"
+ case valueUndefined:
+ s = "undefined"
+ case valueObject:
+ s = v.Class()
+ }
+
+ panic(self.panicTypeError("can't convert from %q to %q", s, t))
+}
+
+func (self *_runtime) toValue(value interface{}) Value {
+ switch value := value.(type) {
+ case Value:
+ return value
+ case func(FunctionCall) Value:
+ var name, file string
+ var line int
+ pc := reflect.ValueOf(value).Pointer()
+ fn := runtime.FuncForPC(pc)
+ if fn != nil {
+ name = fn.Name()
+ file, line = fn.FileLine(pc)
+ file = path.Base(file)
+ }
+ return toValue_object(self.newNativeFunction(name, file, line, value))
+ case _nativeFunction:
+ var name, file string
+ var line int
+ pc := reflect.ValueOf(value).Pointer()
+ fn := runtime.FuncForPC(pc)
+ if fn != nil {
+ name = fn.Name()
+ file, line = fn.FileLine(pc)
+ file = path.Base(file)
+ }
+ return toValue_object(self.newNativeFunction(name, file, line, value))
+ case Object, *Object, _object, *_object:
+ // Nothing happens.
+ // FIXME We should really figure out what can come here.
+ // This catch-all is ugly.
+ default:
+ {
+ value := reflect.ValueOf(value)
+
+ switch value.Kind() {
+ case reflect.Ptr:
+ switch reflect.Indirect(value).Kind() {
+ case reflect.Struct:
+ return toValue_object(self.newGoStructObject(value))
+ case reflect.Array:
+ return toValue_object(self.newGoArray(value))
+ }
+ case reflect.Struct:
+ return toValue_object(self.newGoStructObject(value))
+ case reflect.Map:
+ return toValue_object(self.newGoMapObject(value))
+ case reflect.Slice:
+ return toValue_object(self.newGoSlice(value))
+ case reflect.Array:
+ return toValue_object(self.newGoArray(value))
+ case reflect.Func:
+ var name, file string
+ var line int
+ if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr {
+ pc := v.Pointer()
+ fn := runtime.FuncForPC(pc)
+ if fn != nil {
+ name = fn.Name()
+ file, line = fn.FileLine(pc)
+ file = path.Base(file)
+ }
+ }
+
+ typ := value.Type()
+
+ return toValue_object(self.newNativeFunction(name, file, line, func(c FunctionCall) Value {
+ nargs := typ.NumIn()
+
+ if len(c.ArgumentList) != nargs {
+ if typ.IsVariadic() {
+ if len(c.ArgumentList) < nargs-1 {
+ panic(self.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList))))
+ }
+ } else {
+ panic(self.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList))))
+ }
+ }
+
+ in := make([]reflect.Value, len(c.ArgumentList))
+
+ callSlice := false
+
+ for i, a := range c.ArgumentList {
+ var t reflect.Type
+
+ n := i
+ if n >= nargs-1 && typ.IsVariadic() {
+ if n > nargs-1 {
+ n = nargs - 1
+ }
+
+ t = typ.In(n).Elem()
+ } else {
+ t = typ.In(n)
+ }
+
+ // if this is a variadic Go function, and the caller has supplied
+ // exactly the number of JavaScript arguments required, and this
+ // is the last JavaScript argument, try treating the it as the
+ // actual set of variadic Go arguments. if that succeeds, break
+ // out of the loop.
+ if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 {
+ var v reflect.Value
+ if err := catchPanic(func() { v = self.convertCallParameter(a, typ.In(n)) }); err == nil {
+ in[i] = v
+ callSlice = true
+ break
+ }
+ }
+
+ in[i] = self.convertCallParameter(a, t)
+ }
+
+ var out []reflect.Value
+ if callSlice {
+ out = value.CallSlice(in)
+ } else {
+ out = value.Call(in)
+ }
+
+ switch len(out) {
+ case 0:
+ return Value{}
+ case 1:
+ return self.toValue(out[0].Interface())
+ default:
+ s := make([]interface{}, len(out))
+ for i, v := range out {
+ s[i] = self.toValue(v.Interface())
+ }
+
+ return self.toValue(s)
+ }
+ }))
+ }
+ }
+ }
+
+ return toValue(value)
+}
+
+func (runtime *_runtime) newGoSlice(value reflect.Value) *_object {
+ self := runtime.newGoSliceObject(value)
+ self.prototype = runtime.global.ArrayPrototype
+ return self
+}
+
+func (runtime *_runtime) newGoArray(value reflect.Value) *_object {
+ self := runtime.newGoArrayObject(value)
+ self.prototype = runtime.global.ArrayPrototype
+ return self
+}
+
+func (runtime *_runtime) parse(filename string, src, sm interface{}) (*ast.Program, error) {
+ return parser.ParseFileWithSourceMap(nil, filename, src, sm, 0)
+}
+
+func (runtime *_runtime) cmpl_parse(filename string, src, sm interface{}) (*_nodeProgram, error) {
+ program, err := parser.ParseFileWithSourceMap(nil, filename, src, sm, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ return cmpl_parse(program), nil
+}
+
+func (self *_runtime) parseSource(src, sm interface{}) (*_nodeProgram, *ast.Program, error) {
+ switch src := src.(type) {
+ case *ast.Program:
+ return nil, src, nil
+ case *Script:
+ return src.program, nil, nil
+ }
+
+ program, err := self.parse("", src, sm)
+
+ return nil, program, err
+}
+
+func (self *_runtime) cmpl_runOrEval(src, sm interface{}, eval bool) (Value, error) {
+ result := Value{}
+ cmpl_program, program, err := self.parseSource(src, sm)
+ if err != nil {
+ return result, err
+ }
+ if cmpl_program == nil {
+ cmpl_program = cmpl_parse(program)
+ }
+ err = catchPanic(func() {
+ result = self.cmpl_evaluate_nodeProgram(cmpl_program, eval)
+ })
+ switch result.kind {
+ case valueEmpty:
+ result = Value{}
+ case valueReference:
+ result = result.resolve()
+ }
+ return result, err
+}
+
+func (self *_runtime) cmpl_run(src, sm interface{}) (Value, error) {
+ return self.cmpl_runOrEval(src, sm, false)
+}
+
+func (self *_runtime) cmpl_eval(src, sm interface{}) (Value, error) {
+ return self.cmpl_runOrEval(src, sm, true)
+}
+
+func (self *_runtime) parseThrow(err error) {
+ if err == nil {
+ return
+ }
+ switch err := err.(type) {
+ case parser.ErrorList:
+ {
+ err := err[0]
+ if err.Message == "Invalid left-hand side in assignment" {
+ panic(self.panicReferenceError(err.Message))
+ }
+ panic(self.panicSyntaxError(err.Message))
+ }
+ }
+ panic(self.panicSyntaxError(err.Error()))
+}
+
+func (self *_runtime) cmpl_parseOrThrow(src, sm interface{}) *_nodeProgram {
+ program, err := self.cmpl_parse("", src, sm)
+ self.parseThrow(err) // Will panic/throw appropriately
+ return program
+}
diff --git a/go/lang/security/audit/reflect-makefunc.yaml b/go/lang/security/audit/reflect-makefunc.yaml
new file mode 100644
index 00000000..0200a485
--- /dev/null
+++ b/go/lang/security/audit/reflect-makefunc.yaml
@@ -0,0 +1,26 @@
+rules:
+- id: reflect-makefunc
+ message: >-
+ 'reflect.MakeFunc' detected. This will sidestep protections that are
+ normally afforded by Go's type system. Audit this call and be sure that
+ user input cannot be used to affect the code generated by MakeFunc;
+ otherwise, you will have a serious security vulnerability.
+ metadata:
+ owasp:
+ - A01:2021 - Broken Access Control
+ cwe:
+ - 'CWE-913: Improper Control of Dynamically-Managed Code Resources'
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ references:
+ - https://owasp.org/Top10/A01_2021-Broken_Access_Control
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ severity: ERROR
+ pattern: reflect.MakeFunc(...)
+ languages:
+ - go
diff --git a/go/lang/security/audit/sqli/gosql-sqli.go b/go/lang/security/audit/sqli/gosql-sqli.go
new file mode 100644
index 00000000..57eb97d5
--- /dev/null
+++ b/go/lang/security/audit/sqli/gosql-sqli.go
@@ -0,0 +1,72 @@
+package main
+
+import "database/sql"
+import "fmt"
+
+func bad1() {
+ db, err := sql.Open("mysql", "theUser:thePassword@/theDbName")
+ if err != nil {
+ panic(err)
+ }
+ query = "SELECT name FROM users WHERE age=" + req.FormValue("age")
+ // ruleid: gosql-sqli
+ db.Query(query)
+}
+
+func bad2(db *sql.DB) {
+ query = "SELECT name FROM users WHERE age="
+ query += req.FormValue("age")
+ // ruleid: gosql-sqli
+ db.QueryRow(query)
+}
+
+func bad3(db *sql.DB) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email)
+ // ruleid: gosql-sqli
+ db.Exec(query)
+}
+
+func bad4(db *sql.DB) {
+ // ruleid: gosql-sqli
+ db.Exec("SELECT name FROM users WHERE age=" + req.FormValue("age"))
+}
+
+func bad5(db *sql.DB) {
+ // ruleid: gosql-sqli
+ db.Exec(fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email))
+}
+
+func ok1(db *sql.DB) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email=hello;")
+ // ok: gosql-sqli
+ db.Exec(query)
+}
+
+func ok2(db *sql.DB) {
+ query = "SELECT name FROM users WHERE age=" + "3"
+ // ok: gosql-sqli
+ db.Query(query)
+}
+
+func ok3(db *sql.DB) {
+ query = "SELECT name FROM users WHERE age="
+ query += "3"
+ // ok: gosql-sqli
+ db.Query(query)
+}
+
+func ok4(db *sql.DB) {
+ // ok: gosql-sqli
+ db.Exec("INSERT INTO users(name, email) VALUES($1, $2)",
+ "Jon Calhoun", "jon@calhoun.io")
+}
+
+func ok5(db *sql.DB) {
+ // ok: gosql-sqli
+ db.Exec("SELECT name FROM users WHERE age=" + "3")
+}
+
+func ok6(db *sql.DB) {
+ // ok: gosql-sqli
+ db.Exec(fmt.Sprintf("SELECT * FROM users WHERE email=hello;"))
+}
diff --git a/go/lang/security/audit/sqli/gosql-sqli.yaml b/go/lang/security/audit/sqli/gosql-sqli.yaml
new file mode 100644
index 00000000..fee58cbe
--- /dev/null
+++ b/go/lang/security/audit/sqli/gosql-sqli.yaml
@@ -0,0 +1,63 @@
+rules:
+- id: gosql-sqli
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern: $DB.$METHOD(...,$QUERY,...)
+ - pattern-either:
+ - pattern-inside: |
+ $QUERY = $X + $Y
+ ...
+ - pattern-inside: |
+ $QUERY += $X
+ ...
+ - pattern-inside: |
+ $QUERY = fmt.Sprintf("...", $PARAM1, ...)
+ ...
+ - pattern-not-inside: |
+ $QUERY += "..."
+ ...
+ - pattern-not-inside: |
+ $QUERY = "..." + "..."
+ ...
+ - pattern: $DB.$METHOD(..., $X + $Y, ...)
+ - pattern: $DB.$METHOD(..., fmt.Sprintf("...", $PARAM1, ...), ...)
+ - pattern-either:
+ - pattern-inside: |
+ $DB, ... = sql.Open(...)
+ ...
+ - pattern-inside: |
+ func $FUNCNAME(..., $DB *sql.DB, ...) {
+ ...
+ }
+ - pattern-not: $DB.$METHOD(..., "..." + "...", ...)
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: ^(Exec|ExecContent|Query|QueryContext|QueryRow|QueryRowContext)$
+ languages:
+ - go
+ message: >-
+ Detected string concatenation with a non-literal variable in a "database/sql"
+ Go SQL statement. This could lead to SQL injection if the variable is user-controlled
+ and not properly sanitized. In order to prevent SQL injection,
+ use parameterized queries or prepared statements instead.
+ You can use prepared statements with the 'Prepare' and 'PrepareContext' calls.
+ metadata:
+ cwe:
+ - "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
+ references:
+ - https://golang.org/pkg/database/sql/
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
diff --git a/go/lang/security/audit/sqli/pg-orm-sqli.go b/go/lang/security/audit/sqli/pg-orm-sqli.go
new file mode 100644
index 00000000..8940dd1f
--- /dev/null
+++ b/go/lang/security/audit/sqli/pg-orm-sqli.go
@@ -0,0 +1,149 @@
+package main
+
+import (
+ "fmt"
+ "path"
+
+ "github.com/go-pg/pg/v10"
+ "github.com/go-pg/pg/v10/orm"
+)
+
+func bad1() {
+ db := pg.Connect(&pg.Options{
+ Addr: ":5432",
+ User: "user",
+ Password: "pass",
+ Database: "db_name",
+ })
+ query = "SELECT name FROM users WHERE age=" + req.FormValue("age")
+ // ruleid: pg-orm-sqli
+ err := db.Model(book).
+ Where("id > ?", 100).
+ WhereOr(query).
+ Limit(1).
+ Select()
+}
+
+func bad2() {
+ db := pg.Connect(opt)
+ query = fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email)
+ story := new(Story)
+ // ruleid: pg-orm-sqli
+ err = db.Model(story).
+ Relation("Author").
+ From("Hello").
+ Where("SELECT name FROM users WHERE age=" + req.FormValue("age")).
+ Select()
+ if err != nil {
+ panic(err)
+ }
+}
+
+func bad3() {
+ opt, err := pg.ParseURL("postgres://user:pass@localhost:5432/db_name")
+ if err != nil {
+ panic(err)
+ }
+
+ db := pg.Connect(opt)
+
+ query = "SELECT name FROM users WHERE age="
+ query += req.FormValue("age")
+ // ruleid: pg-orm-sqli
+ err := db.Model(book).
+ Where(query).
+ WhereGroup(func(q *pg.Query) (*pg.Query, error) {
+ q = q.WhereOr("id = 1").
+ WhereOr("id = 2")
+ return q, nil
+ }).
+ Limit(1).
+ Select()
+}
+
+func bad4(db *pg.DB) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email)
+ // ruleid: pg-orm-sqli
+ err := db.Model((*Book)(nil)).
+ Column("author_id").
+ ColumnExpr(query).
+ Group("author_id").
+ Order("book_count DESC").
+ Select(&res)
+}
+
+func bad5(db *pg.DB) {
+ // ruleid: pg-orm-sqli
+ err = db.Model((*Book)(nil)).
+ Column("title", "text").
+ Where("SELECT name FROM users WHERE age=" + req.FormValue("age")).
+ Select()
+}
+
+func bad6(db *pg.DB) {
+ // ruleid: pg-orm-sqli
+ err = db.Model((*Book)(nil)).
+ Column("title", "text").
+ Where(fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email)).
+ Select()
+}
+
+func ok1(db *pg.DB) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email=hello;")
+ // ok: pg-orm-sqli
+ err = db.Model((*Book)(nil)).
+ Column("title", "text").
+ Where(query).
+ Select()
+}
+
+func ok2(db *pg.DB) {
+ query = "SELECT name FROM users WHERE age=" + "3"
+ // ok: pg-orm-sqli
+ err = db.Model((*Book)(nil)).
+ Column("title", "text").
+ ColumnExpr(query).
+ Select()
+}
+
+func ok3(db *pg.DB) {
+ query = "SELECT name FROM users WHERE age="
+ query += "3"
+ // ok: pg-orm-sqli
+ err = db.Model((*Book)(nil)).
+ Column("title", "text").
+ Where(query).
+ Select()
+}
+
+func ok4(db *pg.DB) {
+ // ok: pg-orm-sqli
+ err := db.Model((*Book)(nil)).
+ Column("title", "text").
+ Where("id = ?", 1).
+ Select(&title, &text)
+}
+
+func ok5(db *pg.DB) {
+ // ok: pg-orm-sqli
+ err := db.Model((*Book)(nil)).
+ Column("title", "text").
+ Where("SELECT name FROM users WHERE age=" + "3").
+ Select(&title, &text)
+}
+
+func ok6(db *pg.DB) {
+ // ok: pg-orm-sqli
+ err := db.Model().
+ ColumnExpr(fmt.Sprintf("SELECT * FROM users WHERE email=hello;"))
+}
+
+func ok7() {
+ // ok: pg-orm-sqli
+ path.Join("foo", fmt.Sprintf("%s.baz", "bar"))
+}
+
+func ok8() {
+ // ok: pg-orm-sqli
+ filepath.Join("foo", fmt.Sprintf("%s.baz", "bar"))
+}
diff --git a/go/lang/security/audit/sqli/pg-orm-sqli.yaml b/go/lang/security/audit/sqli/pg-orm-sqli.yaml
new file mode 100644
index 00000000..04a4da6b
--- /dev/null
+++ b/go/lang/security/audit/sqli/pg-orm-sqli.yaml
@@ -0,0 +1,87 @@
+rules:
+ - id: pg-orm-sqli
+ patterns:
+ - pattern-inside: |
+ import (
+ ...
+ "$IMPORT"
+ )
+ ...
+ - metavariable-regex:
+ metavariable: $IMPORT
+ regex: .*go-pg
+ - pattern-either:
+ - patterns:
+ - pattern: $DB.$METHOD(...,$QUERY,...)
+ - pattern-either:
+ - pattern-inside: |
+ $QUERY = $X + $Y
+ ...
+ - pattern-inside: |
+ $QUERY += $X
+ ...
+ - pattern-inside: |
+ $QUERY = fmt.Sprintf("...", $PARAM1, ...)
+ ...
+ - pattern-not-inside: |
+ $QUERY += "..."
+ ...
+ - pattern-not-inside: |
+ $QUERY = "..." + "..."
+ ...
+ - pattern: |
+ $DB.$INTFUNC1(...).$METHOD(..., $X + $Y, ...).$INTFUNC2(...)
+ - pattern: |
+ $DB.$METHOD(..., fmt.Sprintf("...", $PARAM1, ...), ...)
+ - pattern-inside: |
+ $DB = pg.Connect(...)
+ ...
+ - pattern-inside: |
+ func $FUNCNAME(..., $DB *pg.DB, ...) {
+ ...
+ }
+ - pattern-not-inside: |
+ $QUERY = fmt.Sprintf("...", ...,"...", ...)
+ ...
+ - pattern-not-inside: |
+ $QUERY += "..."
+ ...
+ - pattern-not: $DB.$METHOD(...,"...",...)
+ - pattern-not: |
+ $DB.$INTFUNC1(...).$METHOD(..., "...", ...).$INTFUNC2(...)
+ - pattern-not-inside: |
+ $QUERY = "..." + "..."
+ - pattern-not: |
+ "..."
+ - pattern-not: path.Join(...)
+ - pattern-not: filepath.Join(...)
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: ^(Where|WhereOr|Join|GroupExpr|OrderExpr|ColumnExpr)$
+ languages:
+ - go
+ message: Detected string concatenation with a non-literal variable in a go-pg
+ ORM SQL statement. This could lead to SQL injection if the variable is
+ user-controlled and not properly sanitized. In order to prevent SQL
+ injection, do not use strings concatenated with user-controlled input.
+ Instead, use parameterized statements.
+ metadata:
+ cwe:
+ - "CWE-89: Improper Neutralization of Special Elements used in an SQL
+ Command ('SQL Injection')"
+ references:
+ - https://pg.uptrace.dev/queries/
+ category: security
+ technology:
+ - go-pg
+ confidence: LOW
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
diff --git a/go/lang/security/audit/sqli/pg-sqli.go b/go/lang/security/audit/sqli/pg-sqli.go
new file mode 100644
index 00000000..bfa8e0c5
--- /dev/null
+++ b/go/lang/security/audit/sqli/pg-sqli.go
@@ -0,0 +1,111 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/go-pg/pg/v10"
+ "github.com/go-pg/pg/v10/orm"
+)
+
+func bad1() {
+ db := pg.Connect(&pg.Options{
+ Addr: ":5432",
+ User: "user",
+ Password: "pass",
+ Database: "db_name",
+ })
+ query = "SELECT name FROM users WHERE age=" + req.FormValue("age")
+ // ruleid: pg-sqli
+ rows, err := db.ExecContext(query)
+}
+
+func bad2() {
+ opt, err := pg.ParseURL("postgres://user:pass@localhost:5432/db_name")
+ if err != nil {
+ panic(err)
+ }
+
+ db := pg.Connect(opt)
+
+ query = "SELECT name FROM users WHERE age=" + req.FormValue("age")
+ // ruleid: pg-sqli
+ rows, err := db.Exec(ctx, query)
+}
+
+func bad3() {
+ opt, err := pg.ParseURL("postgres://user:pass@localhost:5432/ db_name")
+ if err != nil {
+ panic(err)
+ }
+
+ db := pg.Connect(opt)
+ query = "SELECT name FROM users WHERE age="
+ query += req.FormValue("age")
+ // ruleid: pg-sqli
+ db.QueryContext(ctx, query)
+}
+
+func bad4(db *pg.DB) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email)
+ // ruleid: pg-sqli
+ db.Query(ctx, query)
+}
+
+func bad5(db *pg.DB) {
+ // ruleid: pg-sqli
+ db.Exec(ctx, "SELECT name FROM users WHERE age=" + req.FormValue("age"))
+}
+
+func bad6(db *pg.DB) {
+ // ruleid: pg-sqli
+ db.QueryOne(ctx, fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email))
+}
+
+func ok1(db *pg.DB) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email=hello;")
+ // ok: pg-sqli
+ db.QueryContext(ctx, query)
+}
+
+func ok2(db *pg.DB) {
+ query = "SELECT name FROM users WHERE age=" + "3"
+ // ok: pg-sqli
+ db.Query(ctx, query)
+}
+
+func ok3(db *pg.DB) {
+ query = "SELECT name FROM users WHERE age="
+ query += "3"
+ // ok: pg-sqli
+ db.QueryRowContext(ctx, query)
+}
+
+func ok4(db *pg.DB) {
+ // ok: pg-sqli
+ db.Exec(ctx, "INSERT INTO users(name, email) VALUES($1, $2)",
+ "Jon Calhoun", "jon@calhoun.io")
+}
+
+func ok5(db *pg.DB) {
+ // ok: pg-sqli
+ db.Exec("SELECT name FROM users WHERE age=" + "3")
+}
+
+func ok6(db *pg.DB) {
+ // ok: pg-sqli
+ db.Exec(ctx, fmt.Sprintf("SELECT * FROM users WHERE email=hello;"))
+}
+
+func ok7() {
+ opt, err := pg.ParseURL("postgres://user:pass@localhost:5432/db_name")
+ if err != nil {
+ panic(err)
+ }
+
+ db := pg.Connect(opt)
+ if _, err := db.Prepare("my-query", "select $1::int"); err != nil {
+ panic(err)
+ }
+ // ok: pg-sqli
+ row := db.QueryContext(ctx, "my-query", 10)
+}
diff --git a/go/lang/security/audit/sqli/pg-sqli.yaml b/go/lang/security/audit/sqli/pg-sqli.yaml
new file mode 100644
index 00000000..8594696e
--- /dev/null
+++ b/go/lang/security/audit/sqli/pg-sqli.yaml
@@ -0,0 +1,66 @@
+rules:
+- id: pg-sqli
+ languages:
+ - go
+ message: >-
+ Detected string concatenation with a non-literal variable in a go-pg
+ SQL statement. This could lead to SQL injection if the variable is user-controlled
+ and not properly sanitized. In order to prevent SQL injection,
+ use parameterized queries instead of string concatenation. You can use parameterized
+ queries like so:
+ '(SELECT ? FROM table, data1)'
+ metadata:
+ cwe:
+ - "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
+ references:
+ - https://pg.uptrace.dev/
+ - https://pkg.go.dev/github.com/go-pg/pg/v10
+ category: security
+ technology:
+ - go-pg
+ confidence: LOW
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern: |
+ $DB.$METHOD(...,$QUERY,...)
+ - pattern-either:
+ - pattern-inside: |
+ $QUERY = $X + $Y
+ ...
+ - pattern-inside: |
+ $QUERY += $X
+ ...
+ - pattern-inside: |
+ $QUERY = fmt.Sprintf("...", $PARAM1, ...)
+ ...
+ - pattern-not-inside: |
+ $QUERY += "..."
+ ...
+ - pattern-not-inside: |
+ $QUERY = "..." + "..."
+ ...
+ - pattern: $DB.$METHOD(..., $X + $Y, ...)
+ - pattern: $DB.$METHOD(..., fmt.Sprintf("...", $PARAM1, ...), ...)
+ - pattern-either:
+ - pattern-inside: |
+ $DB = pg.Connect(...)
+ ...
+ - pattern-inside: |
+ func $FUNCNAME(..., $DB *pg.DB, ...) {
+ ...
+ }
+ - pattern-not: $DB.$METHOD(..., "..." + "...", ...)
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: ^(Exec|ExecContext|ExecOne|ExecOneContext|Query|QueryOne|QueryContext|QueryOneContext)$
diff --git a/go/lang/security/audit/sqli/pgx-sqli.go b/go/lang/security/audit/sqli/pgx-sqli.go
new file mode 100644
index 00000000..7c86b0f8
--- /dev/null
+++ b/go/lang/security/audit/sqli/pgx-sqli.go
@@ -0,0 +1,121 @@
+package main
+
+import "database/sql"
+import "fmt"
+
+func bad1() {
+ pgxConfig := pgx.ConnConfig{
+ Host: "localhost",
+ Database: "quetest",
+ User: "quetest",
+ }
+ pgxConnPoolConfig := pgx.ConnPoolConfig{pgxConfig, 3, nil}
+ conn, err := pgx.NewConnPool(pgxConnPoolConfig)
+ if err != nil {
+ log.Fatal(err)
+ }
+ query = "SELECT name FROM users WHERE age=" + req.FormValue("age")
+ // ruleid: pgx-sqli
+ rows, err := conn.Query(query)
+}
+
+func bad2() {
+ conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
+ if err != nil {
+ panic(err)
+ }
+ query = "SELECT name FROM users WHERE age=" + req.FormValue("age")
+ // ruleid: pgx-sqli
+ conn.QueryEx(query)
+}
+
+func bad3() {
+ config, err := pgx.ParseConfig(os.Getenv("DATABASE_URL"))
+ if err != nil {
+ panic(err)
+ }
+ config.Logger = log15adapter.NewLogger(log.New("module", "pgx"))
+
+ conn, err := pgx.ConnectConfig(context.Background(), config)
+
+ query = "SELECT name FROM users WHERE age="
+ query += req.FormValue("age")
+ // ruleid: pgx-sqli
+ conn.QueryRow(query)
+}
+
+func bad4(conn *pgx.Conn) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email)
+ // ruleid: pgx-sqli
+ conn.Exec(query)
+}
+
+func bad4(conn *pgx.Conn) {
+ // ruleid: pgx-sqli
+ conn.Exec("SELECT name FROM users WHERE age=" + req.FormValue("age"))
+}
+
+func bad5(conn *pgx.Conn) {
+ // ruleid: pgx-sqli
+ conn.ExecEx(fmt.Sprintf("SELECT * FROM users WHERE email='%s';", email))
+}
+
+func ok1(conn *pgx.Conn) {
+ query = fmt.Sprintf("SELECT * FROM users WHERE email=hello;")
+ // ok: pgx-sqli
+ conn.QueryRowEx(query)
+}
+
+func ok2(conn *pgx.Conn) {
+ query = "SELECT name FROM users WHERE age=" + "3"
+ // ok: pgx-sqli
+ conn.Query(query)
+}
+
+func ok3(conn *pgx.Conn) {
+ query = "SELECT name FROM users WHERE age="
+ query += "3"
+ // ok: pgx-sqli
+ conn.QueryRow(query)
+}
+
+func ok4(conn *pgx.Conn) {
+ // ok: pgx-sqli
+ conn.Exec("INSERT INTO users(name, email) VALUES($1, $2)",
+ "Jon Calhoun", "jon@calhoun.io")
+}
+
+func ok5(conn *pgx.Conn) {
+ // ok: pgx-sqli
+ conn.Exec("SELECT name FROM users WHERE age=" + "3")
+}
+
+func ok6(conn *pgx.Conn) {
+ // ok: pgx-sqli
+ conn.Exec(fmt.Sprintf("SELECT * FROM users WHERE email=hello;"))
+}
+
+func ok7() {
+ conf := pgx.ConnPoolConfig{
+ ConnConfig: pgx.ConnConfig{
+ Host: "/run/postgresql",
+ User: "postgres",
+ Database: "test",
+ },
+ MaxConnections: 5,
+ }
+ db, err := pgx.NewConnPool(conf)
+ if err != nil {
+ panic(err)
+ }
+ if _, err := db.Prepare("my-query", "select $1::int"); err != nil {
+ panic(err)
+ }
+ // ok: pgx-sqli
+ row := db.QueryRow("my-query", 10)
+ var i int
+ if err := row.Scan(&i); err != nil {
+ panic(err)
+ }
+ fmt.Println(i)
+}
diff --git a/go/lang/security/audit/sqli/pgx-sqli.yaml b/go/lang/security/audit/sqli/pgx-sqli.yaml
new file mode 100644
index 00000000..3d625682
--- /dev/null
+++ b/go/lang/security/audit/sqli/pgx-sqli.yaml
@@ -0,0 +1,70 @@
+rules:
+- id: pgx-sqli
+ languages:
+ - go
+ message: >-
+ Detected string concatenation with a non-literal variable in a pgx
+ Go SQL statement. This could lead to SQL injection if the variable is user-controlled
+ and not properly sanitized. In order to prevent SQL injection,
+ use parameterized queries instead. You can use parameterized queries like so:
+ (`SELECT $1 FROM table`, `data1)
+ metadata:
+ cwe:
+ - "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
+ references:
+ - https://github.com/jackc/pgx
+ - https://pkg.go.dev/github.com/jackc/pgx/v4#hdr-Connection_Pool
+ category: security
+ technology:
+ - pgx
+ confidence: LOW
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: HIGH
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern: $DB.$METHOD(...,$QUERY,...)
+ - pattern-either:
+ - pattern-inside: |
+ $QUERY = $X + $Y
+ ...
+ - pattern-inside: |
+ $QUERY += $X
+ ...
+ - pattern-inside: |
+ $QUERY = fmt.Sprintf("...", $PARAM1, ...)
+ ...
+ - pattern-not-inside: |
+ $QUERY += "..."
+ ...
+ - pattern-not-inside: |
+ $QUERY = "..." + "..."
+ ...
+ - pattern: $DB.$METHOD(..., $X + $Y, ...)
+ - pattern: $DB.$METHOD(..., fmt.Sprintf("...", $PARAM1, ...), ...)
+ - pattern-either:
+ - pattern-inside: |
+ $DB, ... = pgx.Connect(...)
+ ...
+ - pattern-inside: |
+ $DB, ... = pgx.NewConnPool(...)
+ ...
+ - pattern-inside: |
+ $DB, ... = pgx.ConnectConfig(...)
+ ...
+ - pattern-inside: |
+ func $FUNCNAME(..., $DB *pgx.Conn, ...) {
+ ...
+ }
+ - pattern-not: $DB.$METHOD(..., "..." + "...", ...)
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: ^(Exec|ExecEx|Query|QueryEx|QueryRow|QueryRowEx)$
+ severity: ERROR
diff --git a/go/lang/security/audit/unsafe-reflect-by-name.go b/go/lang/security/audit/unsafe-reflect-by-name.go
new file mode 100644
index 00000000..7009e5ee
--- /dev/null
+++ b/go/lang/security/audit/unsafe-reflect-by-name.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "fmt"
+ "reflect"
+)
+
+func (mf mapFmt) Format(s fmt.State, c rune, userInput string) {
+ refVal := mf.m
+ key := keys[i]
+ val := refVal.MapIndex(key)
+
+ // ruleid: unsafe-reflect-by-name
+ meth := key.MethodByName(userInput)
+ meth.Call(nil)[0]
+
+ return
+}
+
+func Test1(job interface{}, userInput string) {
+ jobData := make(map[string]interface{})
+
+ valueJ := reflect.ValueOf(job).Elem()
+
+ // ruleid: unsafe-reflect-by-name
+ jobData["color"] = valueJ.FieldByName(userInput).String()
+
+ return jobData
+}
+
+func OkTest(job interface{}, userInput string) {
+ jobData := make(map[string]interface{})
+
+ valueJ := reflect.ValueOf(job).Elem()
+
+ // ok: unsafe-reflect-by-name
+ meth := valueJ.MethodByName("Name")
+ // ok: unsafe-reflect-by-name
+ jobData["color"] = valueJ.FieldByName("color").String()
+
+ return jobData
+}
diff --git a/go/lang/security/audit/unsafe-reflect-by-name.yaml b/go/lang/security/audit/unsafe-reflect-by-name.yaml
new file mode 100644
index 00000000..5f4879fd
--- /dev/null
+++ b/go/lang/security/audit/unsafe-reflect-by-name.yaml
@@ -0,0 +1,42 @@
+rules:
+- id: unsafe-reflect-by-name
+ patterns:
+ - pattern-either:
+ - pattern: |
+ $SMTH.MethodByName($NAME,...)
+ - pattern: |
+ $SMTH.FieldByName($NAME,...)
+ - pattern-not: |
+ $SMTH.MethodByName("...",...)
+ - pattern-not: |
+ $SMTH.FieldByName("...",...)
+ - pattern-inside: |
+ import "reflect"
+ ...
+ message: >-
+ If an attacker can supply values that the application then uses to determine which
+ method or field to invoke,
+ the potential exists for the attacker to create control flow paths through the
+ application
+ that were not intended by the application developers.
+ This attack vector may allow the attacker to bypass authentication or access control
+ checks
+ or otherwise cause the application to behave in an unexpected manner.
+ metadata:
+ cwe:
+ - "CWE-470: Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')"
+ owasp:
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ severity: WARNING
+ languages:
+ - go
diff --git a/go/lang/security/audit/unsafe.go b/go/lang/security/audit/unsafe.go
new file mode 100644
index 00000000..1922fa3e
--- /dev/null
+++ b/go/lang/security/audit/unsafe.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "fmt"
+ "unsafe"
+
+ foobarbaz "unsafe"
+)
+
+type Fake struct{}
+
+func (Fake) Good() {}
+func main() {
+ unsafeM := Fake{}
+ unsafeM.Good()
+ intArray := [...]int{1, 2}
+ fmt.Printf("\nintArray: %v\n", intArray)
+ intPtr := &intArray[0]
+ fmt.Printf("\nintPtr=%p, *intPtr=%d.\n", intPtr, *intPtr)
+ // ruleid: use-of-unsafe-block
+ addressHolder := uintptr(foobarbaz.Pointer(intPtr)) + unsafe.Sizeof(intArray[0])
+ // ruleid: use-of-unsafe-block
+ intPtr = (*int)(foobarbaz.Pointer(addressHolder))
+ fmt.Printf("\nintPtr=%p, *intPtr=%d.\n\n", intPtr, *intPtr)
+}
diff --git a/go/lang/security/audit/unsafe.yaml b/go/lang/security/audit/unsafe.yaml
new file mode 100644
index 00000000..f1fda274
--- /dev/null
+++ b/go/lang/security/audit/unsafe.yaml
@@ -0,0 +1,24 @@
+rules:
+- id: use-of-unsafe-block
+ message: >-
+ Using the unsafe package in Go gives you low-level memory management and many of the strengths of
+ the C language, but also steps around the type safety of Go and can lead to buffer overflows and
+ possible arbitrary code execution by an attacker.
+ Only use this package if you absolutely know what you're doing.
+ languages: [go]
+ severity: WARNING
+ metadata:
+ cwe:
+ - 'CWE-242: Use of Inherently Dangerous Function'
+ source_rule_url: https://github.com/securego/gosec/blob/master/rules/unsafe.go
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ references:
+ - https://cwe.mitre.org/data/definitions/242.html
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ pattern: unsafe.$FUNC(...)
\ No newline at end of file
diff --git a/go/lang/security/audit/xss/import-text-template.fixed.go b/go/lang/security/audit/xss/import-text-template.fixed.go
new file mode 100644
index 00000000..d406047a
--- /dev/null
+++ b/go/lang/security/audit/xss/import-text-template.fixed.go
@@ -0,0 +1,53 @@
+// cf. https://www.veracode.com/blog/secure-development/use-golang-these-mistakes-could-compromise-your-apps-security
+
+package main
+
+import (
+ "net/http"
+ // ruleid: import-text-template
+ "html/template"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+)
+
+const tmpl = ""
+
+type TodoPageData struct {
+ PageTitle string
+ Todos []Todo
+}
+
+type Todo struct {
+ Title string "json:title"
+ Done bool "json:done"
+}
+
+func (t Todo) ToString() string {
+ bytes, _ := json.Marshal(t)
+ return string(bytes)
+}
+
+func getTodos() []Todo {
+ todos := make([]Todo, 3)
+ raw, _ := ioutil.ReadFile("./todos.json")
+ json.Unmarshal(raw, &todos)
+ return todos
+
+}
+
+func main() {
+ tmpl := template.Must(template.ParseFiles("index.html"))
+
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ data := TodoPageData {
+ PageTitle: "My Todos!",
+ Todos: getTodos(),
+ }
+
+ tmpl.Execute(w, data)
+
+ })
+
+ http.ListenAndServe(":" + os.Getenv("PORT"), nil)
+}
diff --git a/go/lang/security/audit/xss/import-text-template.go b/go/lang/security/audit/xss/import-text-template.go
new file mode 100644
index 00000000..9b3f74bc
--- /dev/null
+++ b/go/lang/security/audit/xss/import-text-template.go
@@ -0,0 +1,53 @@
+// cf. https://www.veracode.com/blog/secure-development/use-golang-these-mistakes-could-compromise-your-apps-security
+
+package main
+
+import (
+ "net/http"
+ // ruleid: import-text-template
+ "text/template"
+ "encoding/json"
+ "io/ioutil"
+ "os"
+)
+
+const tmpl = ""
+
+type TodoPageData struct {
+ PageTitle string
+ Todos []Todo
+}
+
+type Todo struct {
+ Title string "json:title"
+ Done bool "json:done"
+}
+
+func (t Todo) ToString() string {
+ bytes, _ := json.Marshal(t)
+ return string(bytes)
+}
+
+func getTodos() []Todo {
+ todos := make([]Todo, 3)
+ raw, _ := ioutil.ReadFile("./todos.json")
+ json.Unmarshal(raw, &todos)
+ return todos
+
+}
+
+func main() {
+ tmpl := template.Must(template.ParseFiles("index.html"))
+
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ data := TodoPageData {
+ PageTitle: "My Todos!",
+ Todos: getTodos(),
+ }
+
+ tmpl.Execute(w, data)
+
+ })
+
+ http.ListenAndServe(":" + os.Getenv("PORT"), nil)
+}
diff --git a/go/lang/security/audit/xss/import-text-template.yaml b/go/lang/security/audit/xss/import-text-template.yaml
new file mode 100644
index 00000000..fe37bcce
--- /dev/null
+++ b/go/lang/security/audit/xss/import-text-template.yaml
@@ -0,0 +1,42 @@
+rules:
+- id: import-text-template
+ message: >-
+ When working with web applications that involve rendering user-generated
+ content, it's important to properly escape any HTML content to prevent
+ Cross-Site Scripting (XSS) attacks. In Go, the `text/template` package does
+ not automatically escape HTML content, which can leave your application
+ vulnerable to these types of attacks. To mitigate this risk, it's
+ recommended to use the `html/template` package instead, which provides
+ built-in functionality for HTML escaping. By using `html/template` to render
+ your HTML content, you can help to ensure that your web application is more
+ secure and less susceptible to XSS vulnerabilities.
+ metadata:
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ references:
+ - https://www.veracode.com/blog/secure-development/use-golang-these-mistakes-could-compromise-your-apps-security
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ severity: WARNING
+ patterns:
+ - pattern: |
+ import "$IMPORT"
+ - metavariable-regex:
+ metavariable: $IMPORT
+ regex: ^(text/template)$
+ - focus-metavariable: $IMPORT
+ fix: |
+ html/template
+ languages:
+ - go
diff --git a/go/lang/security/audit/xss/no-direct-write-to-responsewriter.go b/go/lang/security/audit/xss/no-direct-write-to-responsewriter.go
new file mode 100644
index 00000000..53f9f66f
--- /dev/null
+++ b/go/lang/security/audit/xss/no-direct-write-to-responsewriter.go
@@ -0,0 +1,77 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func getMovieQuote() map[string]string {
+ m := make(map[string]string)
+ m["quote"] = "I'll be back."
+ m["movie"] = "The Terminator"
+ m["year"] = "1984"
+
+ return m
+}
+
+func healthCheck(w http.ResponseWriter, r *http.Request) {
+ // ok: no-direct-write-to-responsewriter
+ w.Write([]byte("alive"))
+}
+
+func indexPage(w http.ResponseWriter, r *http.Request) {
+ const tme = ``
+
+ const template = `
+
+
+ Random Movie Quotes
+ %s
+ ~%s, %s
+
+ `
+
+ quote := getMovieQuote()
+
+ quoteText := quote["quote"]
+ movie := quote["movie"]
+ year := quote["year"]
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid: no-direct-write-to-responsewriter
+ w.Write([]byte(fmt.Sprintf(template, quoteText, movie, year)))
+}
+
+func errorPage(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ const template = `
+
+
+ error; page not found. go back
+
+ `
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid: no-direct-write-to-responsewriter
+ w.Write([]byte(fmt.Sprintf(template, url)))
+}
+
+func writeErrorResponse(rw *http.ResponseWriter, status int, body string) {
+ (*rw).WriteHeader(status)
+ // ruleid: no-direct-write-to-responsewriter
+ (*rw).Write([]byte(body))
+}
+
+func main() {
+ http.HandleFunc("/", indexPage)
+ http.HandleFunc("/error", errorPage)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/go/lang/security/audit/xss/no-direct-write-to-responsewriter.yaml b/go/lang/security/audit/xss/no-direct-write-to-responsewriter.yaml
new file mode 100644
index 00000000..5577c231
--- /dev/null
+++ b/go/lang/security/audit/xss/no-direct-write-to-responsewriter.yaml
@@ -0,0 +1,46 @@
+rules:
+- id: no-direct-write-to-responsewriter
+ languages:
+ - go
+ message: >-
+ Detected directly writing or similar in 'http.ResponseWriter.write()'.
+ This bypasses HTML escaping that prevents cross-site scripting
+ vulnerabilities. Instead, use the 'html/template' package
+ and render data using 'template.Execute()'.
+ metadata:
+ category: security
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ patterns:
+ - pattern-either:
+ - pattern-inside: |
+ func $HANDLER(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-inside: |
+ func $HANDLER(..., $WRITER *http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-inside: |
+ func(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-either:
+ - pattern: $WRITER.Write(...)
+ - pattern: (*$WRITER).Write(...)
+ - pattern-not: $WRITER.Write([]byte("..."))
+ severity: WARNING
diff --git a/go/lang/security/audit/xss/no-fprintf-to-responsewriter.go b/go/lang/security/audit/xss/no-fprintf-to-responsewriter.go
new file mode 100644
index 00000000..55a3265f
--- /dev/null
+++ b/go/lang/security/audit/xss/no-fprintf-to-responsewriter.go
@@ -0,0 +1,52 @@
+// cf. https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+
+package main
+
+import (
+ "fmt"
+ "net/http"
+)
+
+
+func isValid(token string) bool {
+ return true
+}
+
+func vulnerableHandler(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ tok := r.FormValue("token")
+ if !isValid(tok) {
+ // ruleid:no-fprintf-to-responsewriter
+ fmt.Fprintf(w, "Invalid token: %q", tok)
+ }
+ // ...
+}
+
+// cf. https://github.com/wrfly/container-web-tty//blob/09f891f0d12d0a930f37b675e2eda5784733579a/route/asset/bindata.go#L242
+func dirList(w http.ResponseWriter, r *http.Request, f http.File) {
+ dirs, err := f.Readdir(-1)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ // ok:no-fprintf-to-responsewriter
+ fmt.Fprint(w, "Error reading directory")
+ return
+ }
+ sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ // ok:no-fprintf-to-responsewriter
+ fmt.Fprintf(w, "\n")
+ for _, d := range dirs {
+ name := d.Name()
+ if d.IsDir() {
+ name += "/"
+ }
+ // name may contain '?' or '#', which must be escaped to remain
+ // part of the URL path, and not indicate the start of a query
+ // string or fragment.
+ url := url.URL{Path: filepath.Join(r.RequestURI, name)}
+ // ruleid:no-fprintf-to-responsewriter
+ fmt.Fprintf(w, "%s\n", url.String(), d.Name())
+ }
+ // ok:no-fprintf-to-responsewriter
+ fmt.Fprintf(w, "\n")
+}
diff --git a/go/lang/security/audit/xss/no-fprintf-to-responsewriter.yaml b/go/lang/security/audit/xss/no-fprintf-to-responsewriter.yaml
new file mode 100644
index 00000000..c6be9d36
--- /dev/null
+++ b/go/lang/security/audit/xss/no-fprintf-to-responsewriter.yaml
@@ -0,0 +1,40 @@
+rules:
+- id: no-fprintf-to-responsewriter
+ message: >-
+ Detected 'Fprintf' or similar writing to 'http.ResponseWriter'.
+ This bypasses HTML escaping that prevents cross-site scripting
+ vulnerabilities. Instead, use the 'html/template' package
+ to render data to users.
+ metadata:
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ references:
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ severity: WARNING
+ patterns:
+ - pattern-either:
+ - pattern-inside: |
+ func $HANDLER(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-inside: |
+ func(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-not: fmt.$PRINTF($WRITER, "...")
+ - pattern: fmt.$PRINTF($WRITER, ...)
+ languages:
+ - go
diff --git a/go/lang/security/audit/xss/no-interpolation-in-tag.html b/go/lang/security/audit/xss/no-interpolation-in-tag.html
new file mode 100644
index 00000000..262d656b
--- /dev/null
+++ b/go/lang/security/audit/xss/no-interpolation-in-tag.html
@@ -0,0 +1,27 @@
+From: {{.from_email}}
+To: {{.recipient}}
+Subject: {{.subject}}
+
+
+ {{.message}}
+
+
+
diff --git a/go/lang/security/audit/xss/no-interpolation-in-tag.yaml b/go/lang/security/audit/xss/no-interpolation-in-tag.yaml
new file mode 100644
index 00000000..c1a4beef
--- /dev/null
+++ b/go/lang/security/audit/xss/no-interpolation-in-tag.yaml
@@ -0,0 +1,38 @@
+rules:
+- id: no-interpolation-in-tag
+ message: >-
+ Detected template variable interpolation in an HTML tag.
+ This is potentially vulnerable to cross-site scripting (XSS)
+ attacks because a malicious actor has control over HTML
+ but without the need to use escaped characters. Use explicit
+ tags instead.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://github.com/golang/go/issues/19669
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ category: security
+ technology:
+ - generic
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages:
+ - generic
+ severity: WARNING
+ paths:
+ include:
+ - '*.html'
+ - '*.thtml'
+ - '*.gohtml'
+ - '*.tmpl'
+ - '*.tpl'
+ pattern: <{{ ... }} ... >
diff --git a/go/lang/security/audit/xss/no-interpolation-js-template-string.html b/go/lang/security/audit/xss/no-interpolation-js-template-string.html
new file mode 100644
index 00000000..456bf74b
--- /dev/null
+++ b/go/lang/security/audit/xss/no-interpolation-js-template-string.html
@@ -0,0 +1,23 @@
+From: {{.from_email}}
+To: {{.recipient}}
+Subject: {{.subject}}
+
+
+ {{.message}}
+
+
+
+
+
diff --git a/go/lang/security/audit/xss/no-interpolation-js-template-string.yaml b/go/lang/security/audit/xss/no-interpolation-js-template-string.yaml
new file mode 100644
index 00000000..9015c722
--- /dev/null
+++ b/go/lang/security/audit/xss/no-interpolation-js-template-string.yaml
@@ -0,0 +1,42 @@
+rules:
+- id: no-interpolation-js-template-string
+ message: >-
+ Detected template variable interpolation in a JavaScript
+ template string. This is potentially vulnerable to
+ cross-site scripting (XSS) attacks because a malicious
+ actor has control over JavaScript but without the need
+ to use escaped characters. Instead, obtain this variable
+ outside of the template string and ensure your template
+ is properly escaped.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://github.com/golang/go/issues/9200#issuecomment-66100328
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ category: security
+ technology:
+ - generic
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages:
+ - generic
+ severity: WARNING
+ paths:
+ include:
+ - '*.html'
+ - '*.thtml'
+ - '*.gohtml'
+ - '*.tmpl'
+ - '*.tpl'
+ patterns:
+ - pattern-inside:
+ - pattern: '` ... {{ ... }} ...`'
diff --git a/go/lang/security/audit/xss/no-io-writestring-to-responsewriter.go b/go/lang/security/audit/xss/no-io-writestring-to-responsewriter.go
new file mode 100644
index 00000000..df00f3aa
--- /dev/null
+++ b/go/lang/security/audit/xss/no-io-writestring-to-responsewriter.go
@@ -0,0 +1,31 @@
+// cf. https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+
+package main
+
+import (
+ "fmt"
+ "net/http"
+)
+
+
+func isValid(token string) bool {
+ return true
+}
+
+func vulnerableHandler(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ tok := r.FormValue("token")
+ if !isValid(tok) {
+ // ruleid:no-io-writestring-to-responsewriter
+ io.WriteString(w, fmt.Sprintf("Invalid token: %q", tok))
+ }
+ // ...
+}
+
+// cf. https://github.com/hashicorp/vault-plugin-database-mongodbatlas//blob/9cf156a44f9c8d56fb263f692541e5c7fbab9ab1/vendor/golang.org/x/net/http2/server.go#L2160
+func handleHeaderListTooLong(w http.ResponseWriter, r *http.Request) {
+ const statusRequestHeaderFieldsTooLarge = 431
+ w.WriteHeader(statusRequestHeaderFieldsTooLarge)
+ // ok:no-io-writestring-to-responsewriter
+ io.WriteString(w, "HTTP Error 431
Request Header Field(s) Too Large
")
+}
diff --git a/go/lang/security/audit/xss/no-io-writestring-to-responsewriter.yaml b/go/lang/security/audit/xss/no-io-writestring-to-responsewriter.yaml
new file mode 100644
index 00000000..a359cf05
--- /dev/null
+++ b/go/lang/security/audit/xss/no-io-writestring-to-responsewriter.yaml
@@ -0,0 +1,41 @@
+rules:
+- id: no-io-writestring-to-responsewriter
+ message: >-
+ Detected 'io.WriteString()' writing directly to 'http.ResponseWriter'.
+ This bypasses HTML escaping that prevents cross-site scripting
+ vulnerabilities. Instead, use the 'html/template' package
+ to render data to users.
+ metadata:
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ references:
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ - https://golang.org/pkg/io/#WriteString
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ severity: WARNING
+ patterns:
+ - pattern-either:
+ - pattern-inside: |
+ func $HANDLER(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-inside: |
+ func(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-not: io.WriteString($WRITER, "...")
+ - pattern: io.WriteString($WRITER, $STRING)
+ languages:
+ - go
diff --git a/go/lang/security/audit/xss/no-printf-in-responsewriter.go b/go/lang/security/audit/xss/no-printf-in-responsewriter.go
new file mode 100644
index 00000000..036684f9
--- /dev/null
+++ b/go/lang/security/audit/xss/no-printf-in-responsewriter.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func getMovieQuote() map[string]string {
+ m := make(map[string]string)
+ m["quote"] = "I'll be back."
+ m["movie"] = "The Terminator"
+ m["year"] = "1984"
+
+ return m
+}
+
+func indexPage(w http.ResponseWriter, r *http.Request) {
+ const tme = ``
+
+ const template = `
+
+
+ Random Movie Quotes
+ %s
+ ~%s, %s
+
+ `
+
+ quote := getMovieQuote()
+
+ quoteText := quote["quote"]
+ movie := quote["movie"]
+ year := quote["year"]
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid: no-printf-in-responsewriter
+ w.Write([]byte(fmt.Sprintf(template, quoteText, movie, year)))
+}
+
+func errorPage(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ const template = `
+
+
+ error; page not found. go back
+
+ `
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid: no-printf-in-responsewriter
+ w.Write([]byte(fmt.Sprintf(template, url)))
+}
+
+func main() {
+ http.HandleFunc("/", indexPage)
+ http.HandleFunc("/error", errorPage)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/go/lang/security/audit/xss/no-printf-in-responsewriter.yaml b/go/lang/security/audit/xss/no-printf-in-responsewriter.yaml
new file mode 100644
index 00000000..22ae6872
--- /dev/null
+++ b/go/lang/security/audit/xss/no-printf-in-responsewriter.yaml
@@ -0,0 +1,40 @@
+rules:
+- id: no-printf-in-responsewriter
+ message: >-
+ Detected 'printf' or similar in 'http.ResponseWriter.write()'.
+ This bypasses HTML escaping that prevents cross-site scripting
+ vulnerabilities. Instead, use the 'html/template' package
+ to render data to users.
+ metadata:
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ references:
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ severity: WARNING
+ patterns:
+ - pattern-either:
+ - pattern-inside: |
+ func $HANDLER(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern-inside: |
+ func(..., $WRITER http.ResponseWriter, ...) {
+ ...
+ }
+ - pattern: |
+ $WRITER.Write(<... fmt.$PRINTF(...) ...>, ...)
+ languages:
+ - go
diff --git a/go/lang/security/audit/xss/template-html-does-not-escape.go b/go/lang/security/audit/xss/template-html-does-not-escape.go
new file mode 100644
index 00000000..4f80a792
--- /dev/null
+++ b/go/lang/security/audit/xss/template-html-does-not-escape.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+ "fmt"
+ "html/template"
+ "net/http"
+ "strconv"
+)
+
+func Fine(r *http.Request) template.HTML {
+ // ok: unsafe-template-type
+ return template.HTML("Hello, world
")
+}
+
+func AlsoFine(r *http.Request) template.HTML {
+ // ok: unsafe-template-type
+ return template.HTML("" + "Hello, world
")
+}
+
+func OthersThatAreFine(r *http.Request) template.HTML {
+ // ok: unsafe-template-type
+ a := template.HTMLAttr("Hello, world
")
+ // ok: unsafe-template-type
+ a := template.JS("Hello, world
")
+ // ok: unsafe-template-type
+ a := template.URL("Hello, world
")
+ // ok: unsafe-template-type
+ a := template.CSS("Hello, world
")
+ // ok: unsafe-template-type
+ a := template.Srcset("Hello, world
")
+}
+
+func OthersThatAreNOTFine(r *http.Request, data string) template.HTML {
+ // ruleid: unsafe-template-type
+ a := template.HTMLAttr(fmt.Sprintf("%s
", data))
+ // ruleid: unsafe-template-type
+ a := template.JS(fmt.Sprintf("%s
", data))
+ // ruleid: unsafe-template-type
+ a := template.URL(fmt.Sprintf("%s
", data))
+ // ruleid: unsafe-template-type
+ a := template.CSS(fmt.Sprintf("%s
", data))
+ // ruleid: unsafe-template-type
+ a := template.Srcset(fmt.Sprintf("%s
", data))
+}
+
+func Concat(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ tmpl := "" + customerId + "
"
+
+ // ruleid: unsafe-template-type
+ return template.HTML(tmpl)
+}
+
+func ConcatBranch(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ doIt, err := strconv.ParseBool(r.URL.Query().Get("do"))
+ if err != nil {
+ return template.HTML("")
+ }
+ var tmpl string
+ if doIt {
+ tmpl = "" + customerId + "
"
+ } else {
+ tmpl = ""
+ }
+
+ // ruleid: unsafe-template-type
+ return template.HTML(tmpl)
+}
+
+func ConcatInline(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: unsafe-template-type
+ return template.HTML("" + customerId + "
")
+}
+
+func ConcatInlineOneside(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+
+ // ruleid: unsafe-template-type
+ return template.HTML("" + customerId)
+}
+
+func Formatted(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ tmpl, err := fmt.Printf("%s
", customerId)
+ if err != nil {
+ return template.HTML("")
+ }
+ // ruleid: unsafe-template-type
+ return template.HTML(tmpl)
+}
+
+func FormattedInline(r *http.Request) template.HTML {
+ customerId := r.URL.Query().Get("id")
+ // ruleid: unsafe-template-type
+ return template.HTML(fmt.Sprintf("%s
", customerId))
+}
+
+func main() {}
diff --git a/go/lang/security/audit/xss/template-html-does-not-escape.yaml b/go/lang/security/audit/xss/template-html-does-not-escape.yaml
new file mode 100644
index 00000000..031ae966
--- /dev/null
+++ b/go/lang/security/audit/xss/template-html-does-not-escape.yaml
@@ -0,0 +1,41 @@
+rules:
+- id: unsafe-template-type
+ message: >-
+ Semgrep could not determine that the argument to 'template.HTML()'
+ is a constant. 'template.HTML()' and similar does not escape contents.
+ Be absolutely sure there is no user-controlled data in this
+ template. If user data can reach this template, you may have
+ a XSS vulnerability. Instead, do not use this function and
+ use 'template.Execute()'.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/pkg/html/template/#HTML
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/vulnerability/xss/xss.go#L33
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-not: template.$ANY("..." + "...")
+ - pattern-not: template.$ANY("...")
+ - pattern-either:
+ - pattern: template.HTML(...)
+ - pattern: template.CSS(...)
+ - pattern: template.HTMLAttr(...)
+ - pattern: template.JS(...)
+ - pattern: template.JSStr(...)
+ - pattern: template.Srcset(...)
+ - pattern: template.URL(...)
diff --git a/go/lang/security/audit/xxe/parsing-external-entities-enabled.go b/go/lang/security/audit/xxe/parsing-external-entities-enabled.go
new file mode 100644
index 00000000..269d59e7
--- /dev/null
+++ b/go/lang/security/audit/xxe/parsing-external-entities-enabled.go
@@ -0,0 +1,30 @@
+import (
+ "fmt"
+ "github.com/lestrrat-go/libxml2/parser"
+)
+
+func vuln() {
+ const s = "]>&e;"
+ // ruleid: parsing-external-entities-enabled
+ p := parser.New(parser.XMLParseNoEnt)
+ doc, err := p.ParseString(s)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println("Doc successfully parsed!")
+ fmt.Println(doc)
+}
+
+func not_vuln() {
+ const s = "]>&e;"
+ // ok: parsing-external-entities-enabled
+ p := parser.New()
+ doc, err := p.ParseString(s)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println("Doc successfully parsed!")
+ fmt.Println(doc)
+}
\ No newline at end of file
diff --git a/go/lang/security/audit/xxe/parsing-external-entities-enabled.yaml b/go/lang/security/audit/xxe/parsing-external-entities-enabled.yaml
new file mode 100644
index 00000000..d0ff2178
--- /dev/null
+++ b/go/lang/security/audit/xxe/parsing-external-entities-enabled.yaml
@@ -0,0 +1,33 @@
+rules:
+- id: parsing-external-entities-enabled
+ patterns:
+ - pattern-inside: |
+ import ("github.com/lestrrat-go/libxml2/parser")
+ ...
+ - pattern: $PARSER := parser.New(parser.XMLParseNoEnt)
+ message: >-
+ Detected enabling of "XMLParseNoEnt", which allows parsing of external entities and can lead to XXE
+ if user controlled data is parsed by the library. Instead, do not enable "XMLParseNoEnt" or be sure
+ to adequately sanitize user-controlled data when it is being parsed by this library.
+ languages:
+ - go
+ severity: WARNING
+ metadata:
+ category: security
+ cwe:
+ - 'CWE-611: Improper Restriction of XML External Entity Reference'
+ owasp:
+ - A04:2017 - XML External Entities (XXE)
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://knowledge-base.secureflag.com/vulnerabilities/xml_injection/xml_entity_expansion_go_lang.html
+ - https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing
+ technology:
+ - libxml2
+ confidence: LOW
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
diff --git a/go/lang/security/bad_tmp.go b/go/lang/security/bad_tmp.go
new file mode 100644
index 00000000..92b41c1f
--- /dev/null
+++ b/go/lang/security/bad_tmp.go
@@ -0,0 +1,21 @@
+package unzip
+
+import (
+ "fmt"
+ "io/ioutil"
+)
+
+func main() {
+ // ruleid:bad-tmp-file-creation
+ err := ioutil.WriteFile("/tmp/demo2", []byte("This is some data"), 0644)
+ if err != nil {
+ fmt.Println("Error while writing!")
+ }
+}
+func main_good() {
+ // ok:bad-tmp-file-creation
+ err := ioutil.Tempfile("/tmp", "my_temp")
+ if err != nil {
+ fmt.Println("Error while writing!")
+ }
+}
diff --git a/go/lang/security/bad_tmp.yaml b/go/lang/security/bad_tmp.yaml
new file mode 100644
index 00000000..4633df7e
--- /dev/null
+++ b/go/lang/security/bad_tmp.yaml
@@ -0,0 +1,24 @@
+rules:
+- id: bad-tmp-file-creation
+ message: File creation in shared tmp directory without using ioutil.Tempfile
+ languages: [go]
+ severity: WARNING
+ metadata:
+ cwe:
+ - 'CWE-377: Insecure Temporary File'
+ source-rule-url: https://github.com/securego/gosec
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ owasp:
+ - A01:2021 - Broken Access Control
+ references:
+ - https://owasp.org/Top10/A01_2021-Broken_Access_Control
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ pattern-either:
+ - pattern: ioutil.WriteFile("=~//tmp/.*$/", ...)
+ - pattern: os.Create("=~//tmp/.*$/", ...)
diff --git a/go/lang/security/decompression_bomb.go b/go/lang/security/decompression_bomb.go
new file mode 100644
index 00000000..f1d38dd9
--- /dev/null
+++ b/go/lang/security/decompression_bomb.go
@@ -0,0 +1,103 @@
+// cf. https://github.com/securego/gosec/blob/master/testutils/source.go#L684
+
+package unzip
+
+import (
+ "bytes"
+ "compress/zlib"
+ "io"
+ "os"
+)
+
+func blah() {
+ buff := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207,
+ 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
+ b := bytes.NewReader(buff)
+ r, err := zlib.NewReader(b)
+ if err != nil {
+ panic(err)
+ }
+ // ruleid: potential-dos-via-decompression-bomb
+ _, err := io.Copy(os.Stdout, r)
+ if err != nil {
+ panic(err)
+ }
+ r.Close()
+}
+
+func blah2() {
+ buff := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207,
+ 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
+ b := bytes.NewReader(buff)
+ r, err := zlib.NewReader(b)
+ if err != nil {
+ panic(err)
+ }
+ buf := make([]byte, 8)
+ // ruleid: potential-dos-via-decompression-bomb
+ _, err := io.CopyBuffer(os.Stdout, r, buf)
+ if err != nil {
+ panic(err)
+ }
+ r.Close()
+}
+
+func blah3() {
+ r, err := zip.OpenReader("tmp.zip")
+ if err != nil {
+ panic(err)
+ }
+ defer r.Close()
+ for i, f := range r.File {
+ out, err := os.OpenFile("output"+strconv.Itoa(i), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ if err != nil {
+ panic(err)
+ }
+ rc, err := f.Open()
+ if err != nil {
+ panic(err)
+ }
+ // ruleid: potential-dos-via-decompression-bomb
+ _, err = io.Copy(out, rc)
+ out.Close()
+ rc.Close()
+ if err != nil {
+ panic(err)
+ }
+ }
+}
+
+func benign() {
+ s, err := os.Open("src")
+ if err != nil {
+ panic(err)
+ }
+ defer s.Close()
+ d, err := os.Create("dst")
+ if err != nil {
+ panic(err)
+ }
+ defer d.Close()
+ // ok: potential-dos-via-decompression-bomb
+ _, err = io.Copy(d, s)
+ if err != nil {
+ panic(err)
+ }
+}
+
+func ok() {
+ buff := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207,
+ 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
+ b := bytes.NewReader(buff)
+ r, err := zlib.NewReader(b)
+ if err != nil {
+ panic(err)
+ }
+ buf := make([]byte, 8)
+ // ok: potential-dos-via-decompression-bomb
+ _, err := io.CopyN(os.Stdout, r, buf, 1024*1024*4)
+ if err != nil {
+ panic(err)
+ }
+ r.Close()
+}
diff --git a/go/lang/security/decompression_bomb.yaml b/go/lang/security/decompression_bomb.yaml
new file mode 100644
index 00000000..295d81b5
--- /dev/null
+++ b/go/lang/security/decompression_bomb.yaml
@@ -0,0 +1,62 @@
+rules:
+- id: potential-dos-via-decompression-bomb
+ message: >-
+ Detected a possible denial-of-service via a zip bomb attack. By limiting the max
+ bytes read, you can mitigate this attack.
+ `io.CopyN()` can specify a size.
+ severity: WARNING
+ languages: [go]
+ patterns:
+ - pattern-either:
+ - pattern: io.Copy(...)
+ - pattern: io.CopyBuffer(...)
+ - pattern-either:
+ - pattern-inside: |
+ gzip.NewReader(...)
+ ...
+ - pattern-inside: |
+ zlib.NewReader(...)
+ ...
+ - pattern-inside: |
+ zlib.NewReaderDict(...)
+ ...
+ - pattern-inside: |
+ bzip2.NewReader(...)
+ ...
+ - pattern-inside: |
+ flate.NewReader(...)
+ ...
+ - pattern-inside: |
+ flate.NewReaderDict(...)
+ ...
+ - pattern-inside: |
+ lzw.NewReader(...)
+ ...
+ - pattern-inside: |
+ tar.NewReader(...)
+ ...
+ - pattern-inside: |
+ zip.NewReader(...)
+ ...
+ - pattern-inside: |
+ zip.OpenReader(...)
+ ...
+ fix-regex:
+ regex: (.*)(Copy|CopyBuffer)\((.*?),(.*?)(\)|,.*\))
+ replacement: \1CopyN(\3, \4, 1024*1024*256)
+ metadata:
+ cwe:
+ - 'CWE-400: Uncontrolled Resource Consumption'
+ source-rule-url: https://github.com/securego/gosec
+ references:
+ - https://golang.org/pkg/io/#CopyN
+ - https://github.com/securego/gosec/blob/master/rules/decompression-bomb.go
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ cwe2022-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
diff --git a/go/lang/security/filepath-clean-misuse.fixed.go b/go/lang/security/filepath-clean-misuse.fixed.go
new file mode 100644
index 00000000..b36e5e1d
--- /dev/null
+++ b/go/lang/security/filepath-clean-misuse.fixed.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "io/ioutil"
+ "log"
+ "net/http"
+ "path/filepath"
+ "path"
+ "strings"
+)
+
+const root = "/tmp"
+
+func main() {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/bad1", func(w http.ResponseWriter, r *http.Request) {
+ // ruleid: filepath-clean-misuse
+ filename := filepath.FromSlash(filepath.Clean("/"+strings.Trim(r.URL.Path, "/")))
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ mux.HandleFunc("/bad2", func(w http.ResponseWriter, r *http.Request) {
+ // ruleid: filepath-clean-misuse
+ filename := filepath.FromSlash(filepath.Clean("/"+strings.Trim(r.URL.Path, "/")))
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ mux.HandleFunc("/ok", func(w http.ResponseWriter, r *http.Request) {
+ filename := r.URL.Path
+ // ok: filepath-clean-misuse
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ mux.HandleFunc("/ok2", func(w http.ResponseWriter, r *http.Request) {
+ // ok: filepath-clean-misuse
+ filename := path.Clean("/" + r.URL.Path)
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ server := &http.Server{
+ Addr: "127.0.0.1:50000",
+ Handler: mux,
+ }
+
+ log.Fatal(server.ListenAndServe())
+}
+
+// TODO
+// func NewHandlerWithDefault(root http.FileSystem, handler http.Handler, defaultPath string, gatewayDomains []string) http.Handler {
+// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+// if isGatewayRequest(r) {
+// // s3 signed request reaching the ui handler, return an error response instead of the default path
+// o := operations.Operation{}
+// err := errors.Codes[errors.ERRLakeFSWrongEndpoint]
+// err.Description = fmt.Sprintf("%s (%v)", err.Description, gatewayDomains)
+// o.EncodeError(w, r, err)
+// return
+// }
+// urlPath := r.URL.Path
+// // We want this rule to only fire when urlPath does not have
+// // a slash in front. This if condition ensures there is a slash,
+// // so the line marked 'ok' below should not fire.
+// if !strings.HasPrefix(urlPath, "/") {
+// urlPath = "/" + urlPath
+// r.URL.Path = urlPath
+// }
+// // ok: filepath-clean-misuse
+// _, err := root.Open(path.Clean(urlPath))
+// if err != nil && os.IsNotExist(err) {
+// r.URL.Path = defaultPath
+// }
+// // consistent content-type
+// contentType := gomime.TypeByExtension(filepath.Ext(r.URL.Path))
+// if contentType != "" {
+// w.Header().Set("Content-Type", contentType)
+// }
+// handler.ServeHTTP(w, r)
+// })
+// }
\ No newline at end of file
diff --git a/go/lang/security/filepath-clean-misuse.go b/go/lang/security/filepath-clean-misuse.go
new file mode 100644
index 00000000..52c7611d
--- /dev/null
+++ b/go/lang/security/filepath-clean-misuse.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "io/ioutil"
+ "log"
+ "net/http"
+ "path/filepath"
+ "path"
+ "strings"
+)
+
+const root = "/tmp"
+
+func main() {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/bad1", func(w http.ResponseWriter, r *http.Request) {
+ // ruleid: filepath-clean-misuse
+ filename := filepath.Clean(r.URL.Path)
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ mux.HandleFunc("/bad2", func(w http.ResponseWriter, r *http.Request) {
+ // ruleid: filepath-clean-misuse
+ filename := path.Clean(r.URL.Path)
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ mux.HandleFunc("/ok", func(w http.ResponseWriter, r *http.Request) {
+ filename := r.URL.Path
+ // ok: filepath-clean-misuse
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ mux.HandleFunc("/ok2", func(w http.ResponseWriter, r *http.Request) {
+ // ok: filepath-clean-misuse
+ filename := path.Clean("/" + r.URL.Path)
+ filename := filepath.Join(root, strings.Trim(filename, "/"))
+ contents, err := ioutil.ReadFile(filename)
+ if err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ w.Write(contents)
+ })
+
+ server := &http.Server{
+ Addr: "127.0.0.1:50000",
+ Handler: mux,
+ }
+
+ log.Fatal(server.ListenAndServe())
+}
+
+// TODO
+// func NewHandlerWithDefault(root http.FileSystem, handler http.Handler, defaultPath string, gatewayDomains []string) http.Handler {
+// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+// if isGatewayRequest(r) {
+// // s3 signed request reaching the ui handler, return an error response instead of the default path
+// o := operations.Operation{}
+// err := errors.Codes[errors.ERRLakeFSWrongEndpoint]
+// err.Description = fmt.Sprintf("%s (%v)", err.Description, gatewayDomains)
+// o.EncodeError(w, r, err)
+// return
+// }
+// urlPath := r.URL.Path
+// // We want this rule to only fire when urlPath does not have
+// // a slash in front. This if condition ensures there is a slash,
+// // so the line marked 'ok' below should not fire.
+// if !strings.HasPrefix(urlPath, "/") {
+// urlPath = "/" + urlPath
+// r.URL.Path = urlPath
+// }
+// // ok: filepath-clean-misuse
+// _, err := root.Open(path.Clean(urlPath))
+// if err != nil && os.IsNotExist(err) {
+// r.URL.Path = defaultPath
+// }
+// // consistent content-type
+// contentType := gomime.TypeByExtension(filepath.Ext(r.URL.Path))
+// if contentType != "" {
+// w.Header().Set("Content-Type", contentType)
+// }
+// handler.ServeHTTP(w, r)
+// })
+// }
\ No newline at end of file
diff --git a/go/lang/security/filepath-clean-misuse.yaml b/go/lang/security/filepath-clean-misuse.yaml
new file mode 100644
index 00000000..516b6d85
--- /dev/null
+++ b/go/lang/security/filepath-clean-misuse.yaml
@@ -0,0 +1,58 @@
+rules:
+- id: filepath-clean-misuse
+ message: >-
+ `Clean` is not intended to sanitize against path traversal attacks.
+ This function is for finding the shortest path name equivalent to the given input.
+ Using `Clean` to sanitize file reads may expose this application to
+ path traversal attacks, where an attacker could access arbitrary files on the server.
+ To fix this easily, write this: `filepath.FromSlash(path.Clean("/"+strings.Trim(req.URL.Path, "/")))`
+ However, a better solution is using the `SecureJoin` function in the package `filepath-securejoin`.
+ See https://pkg.go.dev/github.com/cyphar/filepath-securejoin#section-readme.
+ severity: ERROR
+ languages: [go]
+ mode: taint
+ pattern-sources:
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ ($REQUEST : *http.Request).$ANYTHING
+ - pattern: |
+ ($REQUEST : http.Request).$ANYTHING
+ - metavariable-regex:
+ metavariable: $ANYTHING
+ regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
+ pattern-sinks:
+ - patterns:
+ - pattern-either:
+ - pattern: filepath.Clean($...INNER)
+ - pattern: path.Clean($...INNER)
+ pattern-sanitizers:
+ - pattern-either:
+ - pattern: |
+ "/" + ...
+ fix: filepath.FromSlash(filepath.Clean("/"+strings.Trim($...INNER, "/")))
+ options:
+ interfile: true
+ metadata:
+ references:
+ - https://pkg.go.dev/path#Clean
+ - http://technosophos.com/2016/03/31/go-quickly-cleaning-filepaths.html
+ - https://labs.detectify.com/2021/12/15/zero-day-path-traversal-grafana/
+ - https://dzx.cz/2021/04/02/go_path_traversal/
+ - https://pkg.go.dev/github.com/cyphar/filepath-securejoin#section-readme
+ cwe:
+ - "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"
+ owasp:
+ - A05:2017 - Broken Access Control
+ - A01:2021 - Broken Access Control
+ category: security
+ technology:
+ - go
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ confidence: MEDIUM
+ interfile: true
diff --git a/go/lang/security/injection/open-redirect.go b/go/lang/security/injection/open-redirect.go
new file mode 100644
index 00000000..869099d7
--- /dev/null
+++ b/go/lang/security/injection/open-redirect.go
@@ -0,0 +1,48 @@
+package main
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+)
+
+func newRedirectServerFmt(addr string, rootPath string) *http.Server {
+ return &http.Server{
+ Addr: addr,
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ target := fmt.Sprintf("https://%s/path/to/%s", req.Host, req.URL.Path)
+ if rootPath != "" {
+ target += "/" + strings.TrimRight(strings.TrimLeft(rootPath, "/"), "/")
+ }
+ target += req.URL.Path
+ if len(req.URL.RawQuery) > 0 {
+ target += "?" + req.URL.RawQuery
+ }
+ // ruleid: open-redirect
+ http.Redirect(w, req, target, http.StatusTemporaryRedirect)
+ }),
+ }
+}
+
+func newRedirectServerAdd(addr string, rootPath string) *http.Server {
+ return &http.Server{
+ Addr: addr,
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ target := "https://" + req.Host + "/path/to/" + req.URL.Path
+ if rootPath != "" {
+ target += "/" + strings.TrimRight(strings.TrimLeft(rootPath, "/"), "/")
+ }
+ target += req.URL.Path
+ if len(req.URL.RawQuery) > 0 {
+ target += "?" + req.URL.RawQuery
+ }
+ // ruleid: open-redirect
+ http.Redirect(w, req, target, http.StatusTemporaryRedirect)
+ }),
+ }
+}
+
+func main() {
+ newRedirectServerAdd("127.0.0.1:8080", "/test")
+ newRedirectServerFmt("127.0.0.1:8080", "/test")
+}
diff --git a/go/lang/security/injection/open-redirect.yaml b/go/lang/security/injection/open-redirect.yaml
new file mode 100644
index 00000000..6bafe1ed
--- /dev/null
+++ b/go/lang/security/injection/open-redirect.yaml
@@ -0,0 +1,58 @@
+rules:
+ - id: open-redirect
+ languages: [ go ]
+ severity: WARNING
+ message: An HTTP redirect was found to be crafted from user-input `$REQUEST`.
+ This can lead to open redirect vulnerabilities, potentially allowing attackers
+ to redirect users to malicious web sites. It is recommend where possible to
+ not allow user-input to craft the redirect URL. When user-input is necessary
+ to craft the request, it is recommended to follow OWASP best practices to
+ restrict the URL to domains in an allowlist.
+ options:
+ interfile: true
+ metadata:
+ cwe:
+ - "CWE-601: URL Redirection to Untrusted Site ('Open Redirect')"
+ references:
+ - https://knowledge-base.secureflag.com/vulnerabilities/unvalidated_redirects___forwards/open_redirect_go_lang.html
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ description: "An HTTP redirect was found to be crafted from user-input leading to an open redirect vulnerability"
+ subcategory:
+ - vuln
+ impact: MEDIUM
+ likelihood: MEDIUM
+ interfile: true
+ mode: taint
+ pattern-sources:
+ - label: INPUT
+ patterns:
+ - pattern-either:
+ - pattern: |
+ ($REQUEST : *http.Request).$ANYTHING
+ - pattern: |
+ ($REQUEST : http.Request).$ANYTHING
+ - metavariable-regex:
+ metavariable: $ANYTHING
+ regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
+ - label: CLEAN
+ requires: INPUT
+ patterns:
+ - pattern-either:
+ - pattern: |
+ "$URLSTR" + $INPUT
+ - patterns:
+ - pattern-either:
+ - pattern: fmt.Fprintf($F, "$URLSTR", $INPUT, ...)
+ - pattern: fmt.Sprintf("$URLSTR", $INPUT, ...)
+ - pattern: fmt.Printf("$URLSTR", $INPUT, ...)
+ - metavariable-regex:
+ metavariable: $URLSTR
+ regex: .*//[a-zA-Z0-10]+\..*
+ pattern-sinks:
+ - requires: INPUT and not CLEAN
+ patterns:
+ - pattern: http.Redirect($W, $REQ, $URL, ...)
+ - focus-metavariable: $URL
diff --git a/go/lang/security/injection/raw-html-format.go b/go/lang/security/injection/raw-html-format.go
new file mode 100644
index 00000000..1e81b178
--- /dev/null
+++ b/go/lang/security/injection/raw-html-format.go
@@ -0,0 +1,129 @@
+package main
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+)
+
+func getMovieQuote() map[string]string {
+ m := make(map[string]string)
+ m["quote"] = "I'll be back."
+ m["movie"] = "The Terminator"
+ m["year"] = "1984"
+
+ return m
+}
+
+func healthCheck(w http.ResponseWriter, r *http.Request) {
+ // ok: raw-html-format
+ w.Write([]byte("alive"))
+}
+
+func indexPage(w http.ResponseWriter, r *http.Request) {
+ const tme = ``
+
+ const template = `
+
+
+ Random Movie Quotes
+ %s
+ ~%s, %s
+
+ `
+
+ quote := getMovieQuote()
+
+ quoteText := quote["quote"]
+ movie := quote["movie"]
+ year := quote["year"]
+
+ w.WriteHeader(http.StatusAccepted)
+ // ok: raw-html-format
+ w.Write([]byte(fmt.Sprintf(template, quoteText, movie, year)))
+}
+
+func errorPage(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ const template = `
+
+
+ error; page not found. go back
+
+ `
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid:raw-html-format
+ w.Write([]byte(fmt.Sprintf(template, url)))
+}
+
+func errorPage2(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ const template = `
+
+
+ error; page not found. go back
+
+ `
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid:raw-html-format
+ w.Write([]byte(fmt.Printf(template, url)))
+}
+
+func errorPage3(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ const template = `
+
+
+ error; page not found. go back
+
+ `
+
+ w.WriteHeader(http.StatusAccepted)
+ // ruleid:raw-html-format
+ fmt.Fprintf(w, template, url)
+}
+
+func errorPage4(w http.ResponseWriter, r *http.Request) {
+ params := r.URL.Query()
+ urls, ok := params["url"]
+ if !ok {
+ log.Println("Error")
+ return
+ }
+ url := urls[0]
+
+ // ruleid:raw-html-format
+ const template = "error; page not found. go back
"
+
+ w.WriteHeader(http.StatusAccepted)
+ w.Write([]byte(template))
+}
+
+func main() {
+ http.HandleFunc("/", indexPage)
+ http.HandleFunc("/error", errorPage)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/go/lang/security/injection/raw-html-format.yaml b/go/lang/security/injection/raw-html-format.yaml
new file mode 100644
index 00000000..267b2789
--- /dev/null
+++ b/go/lang/security/injection/raw-html-format.yaml
@@ -0,0 +1,54 @@
+rules:
+- id: raw-html-format
+ languages: [go]
+ severity: WARNING
+ message: >-
+ Detected user input flowing into a manually constructed HTML string. You may be
+ accidentally bypassing secure methods
+ of rendering HTML by manually constructing HTML and this could create a cross-site
+ scripting vulnerability, which could
+ let attackers steal sensitive user data. Use the `html/template` package which
+ will safely render HTML instead, or inspect
+ that the HTML is rendered safely.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ references:
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ confidence: MEDIUM
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: HIGH
+ impact: MEDIUM
+ mode: taint
+ pattern-sources:
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ ($REQUEST : *http.Request).$ANYTHING
+ - pattern: |
+ ($REQUEST : http.Request).$ANYTHING
+ - metavariable-regex:
+ metavariable: $ANYTHING
+ regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
+ pattern-sanitizers:
+ - pattern: html.EscapeString(...)
+ pattern-sinks:
+ - patterns:
+ - pattern-either:
+ - pattern: fmt.Printf("$HTMLSTR", ...)
+ - pattern: fmt.Sprintf("$HTMLSTR", ...)
+ - pattern: fmt.Fprintf($W, "$HTMLSTR", ...)
+ - pattern: '"$HTMLSTR" + ...'
+ - metavariable-pattern:
+ metavariable: $HTMLSTR
+ language: generic
+ pattern: <$TAG ...
diff --git a/go/lang/security/injection/tainted-sql-string.go b/go/lang/security/injection/tainted-sql-string.go
new file mode 100644
index 00000000..8c4888f9
--- /dev/null
+++ b/go/lang/security/injection/tainted-sql-string.go
@@ -0,0 +1,145 @@
+package main
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "database/sql"
+)
+
+func DeleteHandler(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+ if del == "del" {
+ // ruleid: tainted-sql-string
+ _, err = db.Exec("DELETE FROM table WHERE Id = " + id)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func DeleteHandlerOk(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ del := req.URL.Query().Get("del")
+ idhtml := req.URL.Query().Get("Id")
+
+ id, _ := strconv.Atoi(idhtml)
+
+ if del == "del" {
+ // ok: tainted-sql-string
+ _, err = db.Exec("DELETE FROM table WHERE Id = " + id)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func SelectHandler(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+ if del == "del" {
+ // ruleid: tainted-sql-string
+ sql := fmt.Sprintf("SELECT * FROM table WHERE Id = %v", id)
+ _, err = db.Exec(sql)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func SelectHandler2(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ var sb strings.Builder
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+ if del == "del" {
+ sb.WriteString("SELECT * FROM table WHERE Id = ")
+ // ruleid: tainted-sql-string
+ sb.WriteString(id)
+
+ sql := sb.String()
+ _, err = db.Exec(sql)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func SelectHandler2ok(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ var sb strings.Builder
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+ if del == "del" {
+ sb.WriteString("SELECT * FROM table WHERE Id = ")
+ // ok: tainted-sql-string
+ sb.WriteString(id)
+
+ sql := "select hello"
+ _, err = db.Exec(sql)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func SelectHandler3(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+ if del == "del" {
+ sql := "SELECT * FROM table WHERE Id = "
+ // ruleid: tainted-sql-string
+ sql += id
+ _, err = db.Exec(sql)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func SelectHandler3(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+ if del == "del" {
+ sql := "SELECT * FROM table WHERE Id = "
+ // ok: tainted-sql-string
+ sql += (id != 3)
+ _, err = db.Exec(sql)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
+
+func SelectHandlerOk(db *sql.DB) func(w http.ResponseWriter, req *http.Request) {
+ return func(w http.ResponseWriter, req *http.Request) {
+ del := req.URL.Query().Get("del")
+ id := req.URL.Query().Get("Id")
+
+ if del == "del" {
+ // ok: tainted-sql-string
+ _, err = db.QueryRow("SELECT * FROM table WHERE Id = $1", id)
+
+ // ok: tainted-sql-string
+ fmt.Fprintf(w, "Deleted %s", id)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/go/lang/security/injection/tainted-sql-string.yaml b/go/lang/security/injection/tainted-sql-string.yaml
new file mode 100644
index 00000000..78f4a3f6
--- /dev/null
+++ b/go/lang/security/injection/tainted-sql-string.yaml
@@ -0,0 +1,83 @@
+rules:
+- id: tainted-sql-string
+ languages: [go]
+ message: >-
+ User data flows into this manually-constructed SQL string. User data
+ can be safely inserted into SQL strings using prepared statements or an
+ object-relational mapper (ORM). Manually-constructed SQL strings is a
+ possible indicator of SQL injection, which could let an attacker steal
+ or manipulate data from the database.
+ Instead, use prepared statements (`db.Query("SELECT * FROM t WHERE id = ?", id)`)
+ or a safe library.
+ options:
+ interfile: true
+ metadata:
+ cwe:
+ - "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')"
+ owasp:
+ - A01:2017 - Injection
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/doc/database/sql-injection
+ - https://www.stackhawk.com/blog/golang-sql-injection-guide-examples-and-prevention/
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: HIGH
+ impact: MEDIUM
+ interfile: true
+ mode: taint
+ severity: ERROR
+ pattern-sources:
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ ($REQUEST : *http.Request).$ANYTHING
+ - pattern: |
+ ($REQUEST : http.Request).$ANYTHING
+ - metavariable-regex:
+ metavariable: $ANYTHING
+ regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
+ pattern-sinks:
+ - patterns:
+ - pattern-either:
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ "$SQLSTR" + ...
+ - patterns:
+ - pattern-inside: |
+ $VAR = "$SQLSTR";
+ ...
+ - pattern: $VAR += ...
+ - patterns:
+ - pattern-inside: |
+ var $SB strings.Builder
+ ...
+ - pattern-inside: |
+ $SB.WriteString("$SQLSTR")
+ ...
+ $SB.String(...)
+ - pattern: |
+ $SB.WriteString(...)
+ - metavariable-regex:
+ metavariable: $SQLSTR
+ regex: (?i)(select|delete|insert|create|update|alter|drop).*
+ - patterns:
+ - pattern-either:
+ - pattern: fmt.Fprintf($F, "$SQLSTR", ...)
+ - pattern: fmt.Sprintf("$SQLSTR", ...)
+ - pattern: fmt.Printf("$SQLSTR", ...)
+ - metavariable-regex:
+ metavariable: $SQLSTR
+ regex: \s*(?i)(select|delete|insert|create|update|alter|drop)\b.*%(v|s|q).*
+ pattern-sanitizers:
+ - pattern-either:
+ - pattern: strconv.Atoi(...)
+ - pattern: |
+ ($X: bool)
diff --git a/go/lang/security/injection/tainted-url-host.go b/go/lang/security/injection/tainted-url-host.go
new file mode 100644
index 00000000..079a796b
--- /dev/null
+++ b/go/lang/security/injection/tainted-url-host.go
@@ -0,0 +1,387 @@
+package main
+
+import (
+ "crypto/tls"
+ "encoding/hex"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+)
+
+func handlerIndexFmt(w http.ResponseWriter, r *http.Request) {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ if r.Method == "POST" && r.URL.Path == "/api" {
+ url := fmt.Sprintf("https://%v/api", r.URL.Query().Get("proxy"))
+
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ w.WriteHeader(500)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
+ return
+ } else {
+ proxy := r.URL.Query()["proxy"]
+ secure := r.URL.Query()["secure"]
+
+ url := ""
+ if secure {
+ url = fmt.Sprintf("https://%s", proxy)
+ } else {
+ url = fmt.Sprintf("http://%q", proxy)
+ }
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+ }
+}
+
+func handlerOtherFmt(w http.ResponseWriter, r *http.Request) {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ if r.Method == "POST" && r.URL.Path == "/api" {
+ url := fmt.Printf("https://%v/api", r.URL.Query().Get("proxy"))
+
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ w.WriteHeader(500)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
+ return
+ } else {
+ proxy := r.URL.Query()["proxy"]
+ secure := r.URL.Query()["secure"]
+
+ url := ""
+ if secure {
+ url = fmt.Fprintf(w, "https://%s", proxy)
+ } else {
+ url = fmt.Fprintf(w, "http://%q", proxy)
+ }
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+ }
+}
+
+func handlerOkFmt(w http.ResponseWriter, r *http.Request) {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ if r.Method == "POST" && r.URL.Path == "/api" {
+ url := fmt.Printf("https://example.com/%v", r.URL.Query().Get("proxy"))
+
+ // ok: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ w.WriteHeader(500)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
+ return
+ } else {
+ proxy := r.URL.Query()["proxy"]
+ secure := r.URL.Query()["secure"]
+
+ url := ""
+ if secure {
+ url = fmt.Sprintf("https://example.com/%s", proxy)
+ } else {
+ url = fmt.Fprintf(w, "http://example.com%q", proxy)
+ }
+ // ok: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+ }
+}
+
+func (s *server) handlerBadFmt(w http.ResponseWriter, r *http.Request) {
+ urls, ok := r.URL.Query()["url"] // extract url from query params
+
+ if !ok {
+ http.Error(w, "url missing", 500)
+ return
+ }
+
+ if len(urls) != 1 {
+ http.Error(w, "url missing", 500)
+ return
+ }
+
+ url := fmt.Sprintf("//%s/path", urls[0])
+
+ // ruleid: tainted-url-host
+ resp, err := http.Get(url) // sink
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ client := &http.Client{}
+
+ // ruleid: tainted-url-host
+ req2, err := http.NewRequest("GET", url, nil)
+ _, err2 := client.Do(req2)
+ if err2 != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ // ok: tainted-url-host
+ _, err3 := http.Get("https://semgrep.dev")
+ if err3 != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ url4 := fmt.Sprintf("ftps://%s/path/to/%s", "test", r.URL.Path)
+ // ok: tainted-url-host
+ _, err4 := http.Get("https://semgrep.dev")
+ if err3 != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ bytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ // Write out the hexdump of the bytes as plaintext.
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ fmt.Fprint(w, hex.Dump(bytes))
+}
+
+func handlerIndexAdd(w http.ResponseWriter, r *http.Request) {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ if r.Method == "POST" && r.URL.Path == "/api" {
+ url := "https://" + r.URL.Query().Get("proxy") + "/api"
+
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ w.WriteHeader(500)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
+ return
+ } else {
+ proxy := r.URL.Query()["proxy"]
+ secure := r.URL.Query()["secure"]
+
+ url := ""
+ if secure {
+ url = "https://" + proxy
+ } else {
+ url = "http://" + proxy
+ }
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+ }
+}
+
+func handlerOtherAdd(w http.ResponseWriter, r *http.Request) {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ if r.Method == "POST" && r.URL.Path == "/api" {
+ url := "https://" + r.URL.Query().Get("proxy") + "/api"
+
+ // ruleid: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ w.WriteHeader(500)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
+ return
+ } else {
+ proxy := r.URL.Query()["proxy"]
+ secure := r.URL.Query()["secure"]
+
+ url := ""
+ if secure {
+ url = "https://example.com/" + proxy
+ } else {
+ url = "http://example.com/api/test/" + proxy
+ }
+ // ok: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+ }
+}
+
+func handlerOkAdd(w http.ResponseWriter, r *http.Request) {
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ }
+
+ client := &http.Client{Transport: tr}
+
+ if r.Method == "POST" && r.URL.Path == "/api" {
+ // ok: tainted-url-host
+ resp, err := client.Post("https://example.com/"+r.URL.Query().Get("proxy"), "application/json", r.Body)
+
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ w.WriteHeader(500)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprintf("{\"host\":\"%v\"}", r.URL.Query().Get("proxy"))))
+ return
+ } else {
+ proxy := r.URL.Query()["proxy"]
+ secure := r.URL.Query()["secure"]
+
+ url := ""
+ if secure {
+ url = "https://example.com/" + proxy
+ } else {
+ url = "http://example.com" + proxy
+ }
+ // ok: tainted-url-host
+ resp, err := client.Post(url, "application/json", r.Body)
+ }
+}
+
+func (s *server) handlerBadAdd(w http.ResponseWriter, r *http.Request) {
+ urls, ok := r.URL.Query()["url"] // extract url from query params
+
+ if !ok {
+ http.Error(w, "url missing", 500)
+ return
+ }
+
+ if len(urls) != 1 {
+ http.Error(w, "url missing", 500)
+ return
+ }
+
+ url := urls[0]
+
+ // ruleid: tainted-url-host
+ resp, err := http.Get(url) // sink
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ client := &http.Client{}
+
+ // ruleid: tainted-url-host
+ req2, err := http.NewRequest("GET", r.URL.Path, nil)
+ _, err2 := client.Do(req2)
+ if err2 != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ // ok: tainted-url-host
+ _, err3 := http.Get("https://semgrep.dev")
+ if err3 != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ url4 := fmt.Sprintf("ftps://%s/path/to/%s", "test", r.URL.Path)
+ // ok: tainted-url-host
+ _, err4 := http.Get("https://semgrep.dev")
+ if err3 != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ defer resp.Body.Close()
+
+ bytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ // Write out the hexdump of the bytes as plaintext.
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ fmt.Fprint(w, hex.Dump(bytes))
+}
+
+func main() {
+ http.HandleFunc("/", handlerIndex)
+ http.HandleFunc("/other", handleOther)
+ http.HandleFunc("/ok", handleOk)
+ http.HandleFunc("/bad", handlerBad)
+ http.ListenAndServe(":8888", nil)
+}
diff --git a/go/lang/security/injection/tainted-url-host.yaml b/go/lang/security/injection/tainted-url-host.yaml
new file mode 100644
index 00000000..999fcaea
--- /dev/null
+++ b/go/lang/security/injection/tainted-url-host.yaml
@@ -0,0 +1,80 @@
+rules:
+ - id: tainted-url-host
+ languages:
+ - go
+ message: A request was found to be crafted from user-input `$REQUEST`. This can
+ lead to Server-Side Request Forgery (SSRF) vulnerabilities, potentially
+ exposing sensitive data. It is recommend where possible to not allow
+ user-input to craft the base request, but to be treated as part of the
+ path or query parameter. When user-input is necessary to craft the
+ request, it is recommended to follow OWASP best practices to prevent
+ abuse, including using an allowlist.
+ options:
+ interfile: true
+ metadata:
+ cwe:
+ - "CWE-918: Server-Side Request Forgery (SSRF)"
+ owasp:
+ - A10:2021 - Server-Side Request Forgery (SSRF)
+ references:
+ - https://goteleport.com/blog/ssrf-attacks/
+ category: security
+ technology:
+ - go
+ confidence: HIGH
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ impact: MEDIUM
+ likelihood: MEDIUM
+ interfile: true
+ mode: taint
+ pattern-sources:
+ - label: INPUT
+ patterns:
+ - pattern-either:
+ - pattern: |
+ ($REQUEST : *http.Request).$ANYTHING
+ - pattern: |
+ ($REQUEST : http.Request).$ANYTHING
+ - metavariable-regex:
+ metavariable: $ANYTHING
+ regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
+ - label: CLEAN
+ requires: INPUT
+ patterns:
+ - pattern-either:
+ - pattern: |
+ "$URLSTR" + $INPUT
+ - patterns:
+ - pattern-either:
+ - pattern: fmt.Fprintf($F, "$URLSTR", $INPUT, ...)
+ - pattern: fmt.Sprintf("$URLSTR", $INPUT, ...)
+ - pattern: fmt.Printf("$URLSTR", $INPUT, ...)
+ - metavariable-regex:
+ metavariable: $URLSTR
+ regex: .*//[a-zA-Z0-10]+\..*
+ pattern-sinks:
+ - requires: INPUT and not CLEAN
+ patterns:
+ - pattern-either:
+ - patterns:
+ - pattern-either:
+ - patterns:
+ - pattern-inside: |
+ $CLIENT := &http.Client{...}
+ ...
+ - pattern: $CLIENT.$METHOD($URL, ...)
+ - pattern: http.$METHOD($URL, ...)
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: ^(Get|Head|Post|PostForm)$
+ - patterns:
+ - pattern: |
+ http.NewRequest("$METHOD", $URL, ...)
+ - metavariable-regex:
+ metavariable: $METHOD
+ regex: ^(GET|HEAD|POST|POSTFORM)$
+ - focus-metavariable: $URL
+ severity: WARNING
\ No newline at end of file
diff --git a/go/lang/security/reverseproxy-director.go b/go/lang/security/reverseproxy-director.go
new file mode 100644
index 00000000..0a7b1762
--- /dev/null
+++ b/go/lang/security/reverseproxy-director.go
@@ -0,0 +1,65 @@
+package main
+
+import (
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+)
+
+func NewProxy(targetHost string) (*httputil.ReverseProxy, error) {
+ url, err := url.Parse(targetHost)
+ if err != nil {
+ return nil, err
+ }
+
+ proxy := httputil.NewSingleHostReverseProxy(url)
+
+ originalDirector := proxy.Director
+ // ruleid: reverseproxy-director
+ proxy.Director = func(req *http.Request) {
+ originalDirector(req)
+ modifyRequest(req)
+ }
+ return proxy, nil
+}
+
+func modifyRequest(req *http.Request) {
+ req.Header.Set("Extra-Header", "nice")
+}
+
+func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ proxy.ServeHTTP(w, r)
+ }
+}
+
+type Fake struct {
+ Director string
+}
+
+func extraCases() {
+ rp := &httputil.ReverseProxy{
+ // ruleid: reverseproxy-director
+ Director: func(req *http.Request) {
+ modifyRequest(req)
+ },
+ }
+ _ = rp
+
+ f := Fake{
+ // ok: reverseproxy-director
+ Director: "abcd",
+ }
+ _ = f
+}
+
+func main() {
+ proxy, err := NewProxy("https://example.com")
+ if err != nil {
+ panic(err)
+ }
+
+ http.HandleFunc("/", ProxyRequestHandler(proxy))
+ log.Fatal(http.ListenAndServe(":8080", nil))
+}
diff --git a/go/lang/security/reverseproxy-director.yaml b/go/lang/security/reverseproxy-director.yaml
new file mode 100644
index 00000000..ba210b6c
--- /dev/null
+++ b/go/lang/security/reverseproxy-director.yaml
@@ -0,0 +1,33 @@
+rules:
+- id: reverseproxy-director
+ message: >-
+ ReverseProxy can remove headers added by Director. Consider using ReverseProxy.Rewrite
+ instead of ReverseProxy.Director.
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-inside: |
+ import "net/http/httputil"
+ ...
+ - pattern-either:
+ - pattern: $PROXY.Director = $FUNC
+ - patterns:
+ - pattern-inside: |
+ httputil.ReverseProxy{
+ ...
+ }
+ - pattern: |
+ Director: $FUNC
+ metadata:
+ cwe:
+ - "CWE-115: Misinterpretation of Input"
+ category: security
+ subcategory:
+ - audit
+ technology:
+ - go
+ confidence: MEDIUM
+ likelihood: LOW
+ impact: LOW
+ references:
+ - https://github.com/golang/go/issues/50580
diff --git a/go/lang/security/shared-url-struct-mutation.go b/go/lang/security/shared-url-struct-mutation.go
new file mode 100644
index 00000000..005d2007
--- /dev/null
+++ b/go/lang/security/shared-url-struct-mutation.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+ "net/http"
+ "net/url"
+)
+
+var redirectURL, _ = url.Parse("https://example.com")
+
+func getRedirectToken() (string, error) {
+ return "abcd", nil
+}
+
+func handler1(w http.ResponseWriter, r *http.Request) {
+ u := redirectURL
+ q := u.Query()
+
+ // opaque process that might fail
+ token, err := getRedirectToken()
+ if err != nil {
+ q.Set("error", err.Error())
+ } else {
+ q.Set("token", token)
+ }
+ // ruleid: shared-url-struct-mutation
+ u.RawQuery = q.Encode()
+ r.URL.RawQuery = q.Encode()
+
+ http.Redirect(w, r, u.String(), http.StatusFound)
+}
+
+func handler2(w http.ResponseWriter, r *http.Request) {
+ u, _ := url.Parse("https://example.com")
+
+ q := u.Query()
+
+ // opaque process that might fail
+ token, err := getRedirectToken()
+ if err != nil {
+ q.Set("error", err.Error())
+ } else {
+ q.Set("token", token)
+ }
+ // ok: shared-url-struct-mutation
+ u.RawQuery = q.Encode()
+
+ http.Redirect(w, r, u.String(), http.StatusFound)
+}
+
+func handler3(w http.ResponseWriter, r *http.Request) {
+ u := url.URL{
+ Scheme: "https",
+ Host: "example.com",
+ Path: "/",
+ }
+ q := u.Query()
+
+ // opaque process that might fail
+ token, err := getRedirectToken()
+ if err != nil {
+ q.Set("error", err.Error())
+ } else {
+ q.Set("token", token)
+ }
+
+ u.RawQuery = q.Encode()
+
+ http.Redirect(w, r, u.String(), http.StatusFound)
+}
+
+func handler4(w http.ResponseWriter, r *http.Request) {
+ var u *url.URL
+ if true {
+ u, _ = url.Parse("https://example.com")
+ }
+
+ if u != nil {
+
+ q := u.Query()
+
+ // opaque process that might fail
+ token, err := getRedirectToken()
+ if err != nil {
+ q.Set("error", err.Error())
+ } else {
+ q.Set("token", token)
+ }
+ // ok: shared-url-struct-mutation
+ u.RawQuery = q.Encode()
+
+ http.Redirect(w, r, u.String(), http.StatusFound)
+ }
+ http.Redirect(w, r, "https://google.com", http.StatusFound)
+}
+
+func extraCases(w http.ResponseWriter, r *http.Request) {
+ var x struct {
+ y []struct {
+ Path string
+ }
+ }
+ // ok: shared-url-struct-mutation
+ r.URL.RawQuery = "abcd"
+ // ok: shared-url-struct-mutation
+ x.y[0].Path = "abcd"
+
+ a, _ := url.ParseRequestURI("https://example.com")
+ // ok: shared-url-struct-mutation
+ a.RawQuery = "abcd"
+}
+
+func main() {
+ http.HandleFunc("/1", handler1)
+ http.HandleFunc("/2", handler2)
+ http.HandleFunc("/3", handler3)
+ http.HandleFunc("/4", handler4)
+ http.ListenAndServe(":7777", nil)
+}
diff --git a/go/lang/security/shared-url-struct-mutation.yaml b/go/lang/security/shared-url-struct-mutation.yaml
new file mode 100644
index 00000000..0dcd483a
--- /dev/null
+++ b/go/lang/security/shared-url-struct-mutation.yaml
@@ -0,0 +1,52 @@
+rules:
+- id: shared-url-struct-mutation
+ message: >-
+ Shared URL struct may have been accidentally mutated. Ensure that
+ this behavior is intended.
+ languages: [go]
+ severity: WARNING
+ patterns:
+ - pattern-inside: |
+ import "net/url"
+ ...
+ - pattern-not-inside: |
+ ... = url.Parse(...)
+ ...
+ - pattern-not-inside: |
+ ... = url.ParseRequestURI(...)
+ ...
+ - pattern-not-inside: |
+ ... = url.URL{...}
+ ...
+ - pattern-not-inside: |
+ var $URL *$X.URL
+ ...
+ - pattern-either:
+ - pattern: $URL.RawQuery = ...
+ - pattern: $URL.Path = ...
+ - pattern: $URL.RawPath = ...
+ - pattern: $URL.Fragment = ...
+ - pattern: $URL.RawFragment = ...
+ - pattern: $URL.Scheme = ...
+ - pattern: $URL.Opaque = ...
+ - pattern: $URL.Host = ...
+ - pattern: $URL.User = ...
+ - metavariable-pattern:
+ metavariable: $URL
+ patterns:
+ - pattern-not: $X.$Y
+ - pattern-not: $X[...]
+ metadata:
+ cwe:
+ - "CWE-436: Interpretation Conflict"
+ category: security
+ subcategory:
+ - audit
+ technology:
+ - go
+ confidence: LOW
+ likelihood: LOW
+ impact: LOW
+ references:
+ - https://github.com/golang/go/issues/63777
+
diff --git a/go/lang/security/zip.go b/go/lang/security/zip.go
new file mode 100644
index 00000000..5f0e2e23
--- /dev/null
+++ b/go/lang/security/zip.go
@@ -0,0 +1,75 @@
+package unzip
+
+import (
+ "archive/zip"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "path/filepath"
+)
+
+func unzip(archive, target string) error {
+ // ruleid: path-traversal-inside-zip-extraction
+ reader, err := zip.OpenReader(archive)
+ if err != nil {
+ return err
+ }
+
+ if err := os.MkdirAll(target, 0750); err != nil {
+ return err
+ }
+
+ for _, file := range reader.File {
+ path := filepath.Join(target, file.Name)
+ if file.FileInfo().IsDir() {
+ os.MkdirAll(path, file.Mode()) // #nosec
+ continue
+ }
+
+ fileReader, err := file.Open()
+ if err != nil {
+ return err
+ }
+ defer fileReader.Close()
+
+ targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
+ if err != nil {
+ return err
+ }
+ defer targetFile.Close()
+
+ if _, err := io.Copy(targetFile, fileReader); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func unzip_good() {
+ // Open a zip archive for reading.
+ r, err := zip.OpenReader("testdata/readme.zip")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer r.Close()
+ // Iterate through the files in the archive,
+ // printing some of their contents.
+ for _, f := range r.File {
+ fmt.Printf("Contents of %s:\n", f.Name)
+ rc, err := f.Open()
+ if err != nil {
+ log.Fatal(err)
+ }
+ _, err = io.CopyN(os.Stdout, rc, 68)
+ if err != nil {
+ log.Fatal(err)
+ }
+ rc.Close()
+ fmt.Println()
+ }
+ // Output:
+ // Contents of README:
+ // This is the source code repository for the Go programming language.
+}
diff --git a/go/lang/security/zip.yaml b/go/lang/security/zip.yaml
new file mode 100644
index 00000000..e03aa57b
--- /dev/null
+++ b/go/lang/security/zip.yaml
@@ -0,0 +1,32 @@
+rules:
+- id: path-traversal-inside-zip-extraction
+ message: File traversal when extracting zip archive
+ metadata:
+ cwe:
+ - "CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')"
+ source_rule_url: https://github.com/securego/gosec/issues/205
+ category: security
+ technology:
+ - go
+ confidence: LOW
+ owasp:
+ - A05:2017 - Broken Access Control
+ - A01:2021 - Broken Access Control
+ references:
+ - https://owasp.org/Top10/A01_2021-Broken_Access_Control
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ languages: [go]
+ severity: WARNING
+ pattern: |
+ reader, $ERR := zip.OpenReader($ARCHIVE)
+ ...
+ for _, $FILE := range reader.File {
+ ...
+ path := filepath.Join($TARGET, $FILE.Name)
+ ...
+ }
diff --git a/go/otto/security/audit/dangerous-execution.go b/go/otto/security/audit/dangerous-execution.go
new file mode 100644
index 00000000..c3ea4fbd
--- /dev/null
+++ b/go/otto/security/audit/dangerous-execution.go
@@ -0,0 +1,28 @@
+package blah
+
+import (
+ "net/http"
+ "github.com/robertkrimen/otto"
+)
+
+func whyyyy(w http.ResponseWriter, r *http.Request) {
+ err := r.ParseForm()
+ if err != nil {
+ panic(err)
+ }
+ script := r.Form.Get("script")
+
+ vm := otto.New()
+
+ // ruleid: dangerous-execution
+ vm.Run(script)
+}
+
+func main() {
+ vm := otto.New()
+ // ok: dangerous-execution
+ vm.Run(`
+ abc = 2 + 2;
+ console.log("The value of abc is " + abc); // 4
+ `)
+}
diff --git a/go/otto/security/audit/dangerous-execution.yaml b/go/otto/security/audit/dangerous-execution.yaml
new file mode 100644
index 00000000..9a1c8d35
--- /dev/null
+++ b/go/otto/security/audit/dangerous-execution.yaml
@@ -0,0 +1,33 @@
+rules:
+- id: dangerous-execution
+ message: >-
+ Detected non-static script inside otto VM. Audit the input to 'VM.Run'.
+ If unverified user data can reach this call site, this is a code injection
+ vulnerability. A malicious actor can inject a malicious script to execute
+ arbitrary code.
+ metadata:
+ cwe:
+ - "CWE-94: Improper Control of Generation of Code ('Code Injection')"
+ owasp:
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - otto
+ - vm
+ confidence: LOW
+ references:
+ - https://owasp.org/Top10/A03_2021-Injection
+ cwe2022-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: HIGH
+ severity: ERROR
+ patterns:
+ - pattern-inside: |
+ $VM = otto.New(...)
+ ...
+ - pattern-not: $VM.Run("...", ...)
+ - pattern: $VM.Run(...)
+ languages:
+ - go
diff --git a/go/template/security/insecure-types.go b/go/template/security/insecure-types.go
new file mode 100644
index 00000000..96adee70
--- /dev/null
+++ b/go/template/security/insecure-types.go
@@ -0,0 +1,31 @@
+package main
+
+import "fmt"
+import "html/template"
+
+func main() {
+ var g = "foo"
+
+ // ruleid:go-insecure-templates
+ const a template.HTML = fmt.Sprintf("link")
+ // ruleid:go-insecure-templates
+ var b template.CSS = "a { text-decoration: underline; } "
+
+ // ruleid:go-insecure-templates
+ var c template.HTMLAttr = fmt.Sprintf("herf=%q")
+
+ // ruleid:go-insecure-templates
+ const d template.JS = "{foo: 'bar'}"
+
+ // ruleid:go-insecure-templates
+ var e template.JSStr = "setTimeout('alert()')";
+
+ // ruleid:go-insecure-templates
+ var f template.Srcset = g;
+
+ // ok:go-insecure-templates
+ tmpl, err := template.New("test").ParseFiles("file.txt")
+
+ // other code
+ myTpl.Execute(w, a);
+}
diff --git a/go/template/security/insecure-types.yaml b/go/template/security/insecure-types.yaml
new file mode 100644
index 00000000..3ea82462
--- /dev/null
+++ b/go/template/security/insecure-types.yaml
@@ -0,0 +1,37 @@
+rules:
+- id: go-insecure-templates
+ patterns:
+ - pattern-inside: |
+ import "html/template"
+ ...
+ - pattern-either:
+ - pattern: var $VAR template.HTML = $EXP
+ - pattern: var $VAR template.CSS = $EXP
+ - pattern: var $VAR template.HTMLAttr = $EXP
+ - pattern: var $VAR template.JS = $EXP
+ - pattern: var $VAR template.JSStr = $EXP
+ - pattern: var $VAR template.Srcset = $EXP
+ message: >-
+ usage of insecure template types. They are documented as a security risk. See https://golang.org/pkg/html/template/#HTML.
+ severity: WARNING
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')"
+ references:
+ - https://golang.org/pkg/html/template/#HTML
+ - https://twitter.com/empijei/status/1275177219011350528
+ category: security
+ technology:
+ - template
+ confidence: LOW
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ languages:
+ - go
diff --git a/go/template/security/ssti.go b/go/template/security/ssti.go
new file mode 100644
index 00000000..99269bab
--- /dev/null
+++ b/go/template/security/ssti.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+ "fmt"
+ "html/template"
+ "net/http"
+)
+
+type User struct {
+ ID int
+ Email string
+ Password string
+}
+
+func match1(w http.ResponseWriter, req *http.Request) {
+
+ var user1 = &User{1, "user@gmail.com", "Sup3rSecr3t123!"}
+ query := req.URL.Query().Get("query")
+ // ruleid:go-ssti
+ var text = fmt.Sprintf(`
+
+
+ SSTI
+
+
+ Hello {{ .Email }}
+
Search result for %s
+
+ `, query)
+ tmpl := template.New("hello")
+ tmpl, err := tmpl.Parse(text)
+ if err != nil {
+ fmt.Println(err)
+ }
+ tmpl.Execute(w, user1)
+}
+
+func match2(w http.ResponseWriter, req *http.Request) {
+
+ var user1 = &User{1, "user@gmail.com", "Sup3rSecr3t123!"}
+ if err := req.ParseForm(); err != nil {
+ fmt.Fprintf(w, "ParseForm() err: %v", err)
+ return
+ }
+ query := req.Form.Get("query")
+ // ruleid:go-ssti
+ var text = fmt.Sprintf(`
+
+
+ SSTI
+
+
+ Hello {{ .Email }}
+ Search result for %s
+
+ `, query)
+ tmpl := template.New("hello")
+ tmpl, err := tmpl.Parse(text)
+ if err != nil {
+ fmt.Println(err)
+ }
+ tmpl.Execute(w, user1)
+}
+
+func no_match(w http.ResponseWriter, req *http.Request) {
+
+ var user1 = &User{1, "user@gmail.com", "Sup3rSecr3t123!"}
+ query := "constant string"
+ // ok:go-ssti
+ var text = fmt.Sprintf(`
+
+
+ SSTI
+
+
+ Hello {{ .Email }}
+ Search result for %s
+
+ `, query)
+ tmpl := template.New("hello")
+ tmpl, err := tmpl.Parse(text)
+ if err != nil {
+ fmt.Println(err)
+ }
+ tmpl.Execute(w, user1)
+}
diff --git a/go/template/security/ssti.yaml b/go/template/security/ssti.yaml
new file mode 100644
index 00000000..dcac32e3
--- /dev/null
+++ b/go/template/security/ssti.yaml
@@ -0,0 +1,56 @@
+rules:
+- id: go-ssti
+ patterns:
+ - pattern-inside: |
+ import ("html/template")
+ ...
+ - pattern: $TEMPLATE = fmt.Sprintf("...", $ARG, ...)
+ - patterns:
+ - pattern-either:
+ - pattern-inside: |
+ func $FN(..., $REQ *http.Request, ...){
+ ...
+ }
+ - pattern-inside: |
+ func $FN(..., $REQ http.Request, ...){
+ ...
+ }
+ - pattern-inside: |
+ func(..., $REQ *http.Request, ...){
+ ...
+ }
+ - patterns:
+ - pattern-either:
+ - pattern-inside: |
+ $ARG := $REQ.URL.Query().Get(...)
+ ...
+ $T, $ERR := $TMPL.Parse($TEMPLATE)
+ - pattern-inside: |
+ $ARG := $REQ.Form.Get(...)
+ ...
+ $T, $ERR := $TMPL.Parse($TEMPLATE)
+ - pattern-inside: |
+ $ARG := $REQ.PostForm.Get(...)
+ ...
+ $T, $ERR := $TMPL.Parse($TEMPLATE)
+ message: >-
+ A server-side template injection occurs when an attacker is able to use
+ native template syntax to inject a malicious payload into a template, which is then executed server-side.
+ When using "html/template" always check that user inputs are validated and sanitized before included
+ within the template.
+ languages: [go]
+ severity: ERROR
+ metadata:
+ category: security
+ cwe:
+ - 'CWE-1336: Improper Neutralization of Special Elements Used in a Template Engine'
+ references:
+ - https://www.onsecurity.io/blog/go-ssti-method-research/
+ - http://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: HIGH
\ No newline at end of file
diff --git a/tmp/rules/owasp/injection-raw-html-format.yaml b/tmp/rules/owasp/injection-raw-html-format.yaml
new file mode 100644
index 00000000..7eb3c145
--- /dev/null
+++ b/tmp/rules/owasp/injection-raw-html-format.yaml
@@ -0,0 +1,448 @@
+rules:
+ - id: raw-html-format
+ languages:
+ - go
+ severity: WARNING
+ message: Detected user input flowing into a manually constructed HTML string.
+ You may be accidentally bypassing secure methods of rendering HTML by
+ manually constructing HTML and this could create a cross-site scripting
+ vulnerability, which could let attackers steal sensitive user data. Use
+ the `html/template` package which will safely render HTML instead, or
+ inspect that the HTML is rendered safely.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation
+ ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ category: security
+ technology:
+ - go
+ references:
+ - https://blogtitle.github.io/robn-go-security-pearls-cross-site-scripting-xss/
+ confidence: MEDIUM
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - vuln
+ likelihood: HIGH
+ impact: MEDIUM
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cross-Site-Scripting (XSS)
+ mode: taint
+ pattern-sources:
+ - patterns:
+ - pattern-either:
+ - pattern: |
+ ($REQUEST : *http.Request).$ANYTHING
+ - pattern: |
+ ($REQUEST : http.Request).$ANYTHING
+ - metavariable-regex:
+ metavariable: $ANYTHING
+ regex: ^(BasicAuth|Body|Cookie|Cookies|Form|FormValue|GetBody|Host|MultipartReader|ParseForm|ParseMultipartForm|PostForm|PostFormValue|Referer|RequestURI|Trailer|TransferEncoding|UserAgent|URL)$
+ pattern-sanitizers:
+ - pattern: html.EscapeString(...)
+ pattern-sinks:
+ - patterns:
+ - pattern-either:
+ - pattern: fmt.Printf("$HTMLSTR", ...)
+ - pattern: fmt.Sprintf("$HTMLSTR", ...)
+ - pattern: fmt.Fprintf($W, "$HTMLSTR", ...)
+ - pattern: '"$HTMLSTR" + ...'
+ - metavariable-pattern:
+ metavariable: $HTMLSTR
+ language: generic
+ pattern: <$TAG ...
+ - id: session-cookie-missing-secure
+ patterns:
+ - pattern-not-inside: |
+ &sessions.Options{
+ ...,
+ Secure: true,
+ ...,
+ }
+ - pattern: |
+ &sessions.Options{
+ ...,
+ }
+ message: A session cookie was detected without setting the 'Secure' flag. The
+ 'secure' flag for cookies prevents the client from transmitting the cookie
+ over insecure channels such as HTTP. Set the 'Secure' flag by setting
+ 'Secure' to 'true' in the Options struct.
+ metadata:
+ cwe:
+ - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/user/session/session.go#L69
+ category: security
+ technology:
+ - gorilla
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (Secure\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages:
+ - go
+
+ - id: session-cookie-samesitenone
+ patterns:
+ - pattern-inside: |
+ &sessions.Options{
+ ...,
+ SameSite: http.SameSiteNoneMode,
+ ...,
+ }
+ - pattern: |
+ &sessions.Options{
+ ...,
+ }
+ message: Found SameSiteNoneMode setting in Gorilla session options. Consider
+ setting SameSite to Lax, Strict or Default for enhanced security.
+ metadata:
+ cwe:
+ - "CWE-1275: Sensitive Cookie with Improper SameSite Attribute"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://pkg.go.dev/github.com/gorilla/sessions#Options
+ category: security
+ technology:
+ - gorilla
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (SameSite\s*:\s+)http.SameSiteNoneMode
+ replacement: \1http.SameSiteDefaultMode
+ severity: WARNING
+ languages:
+ - go
+
+ - id: session-cookie-missing-secure
+ patterns:
+ - pattern-not-inside: |
+ &sessions.Options{
+ ...,
+ Secure: true,
+ ...,
+ }
+ - pattern: |
+ &sessions.Options{
+ ...,
+ }
+ message: A session cookie was detected without setting the 'Secure' flag. The
+ 'secure' flag for cookies prevents the client from transmitting the cookie
+ over insecure channels such as HTTP. Set the 'Secure' flag by setting
+ 'Secure' to 'true' in the Options struct.
+ metadata:
+ cwe:
+ - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/user/session/session.go#L69
+ category: security
+ technology:
+ - gorilla
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (Secure\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages:
+ - go
+
+ - id: session-cookie-missing-httponly
+ patterns:
+ - pattern-not-inside: |
+ &sessions.Options{
+ ...,
+ HttpOnly: true,
+ ...,
+ }
+ - pattern: |
+ &sessions.Options{
+ ...,
+ }
+ message: A session cookie was detected without setting the 'HttpOnly' flag. The
+ 'HttpOnly' flag for cookies instructs the browser to forbid client-side
+ scripts from reading the cookie which mitigates XSS attacks. Set the
+ 'HttpOnly' flag by setting 'HttpOnly' to 'true' in the Options struct.
+ metadata:
+ cwe:
+ - "CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/user/session/session.go#L69
+ category: security
+ technology:
+ - gorilla
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (HttpOnly\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages:
+ - go
+
+ - id: cookie-missing-secure
+ patterns:
+ - pattern-not-inside: |
+ http.Cookie{
+ ...,
+ Secure: true,
+ ...,
+ }
+ - pattern: |
+ http.Cookie{
+ ...,
+ }
+ message: A session cookie was detected without setting the 'Secure' flag. The
+ 'secure' flag for cookies prevents the client from transmitting the cookie
+ over insecure channels such as HTTP. Set the 'Secure' flag by setting
+ 'Secure' to 'true' in the Options struct.
+ metadata:
+ cwe:
+ - "CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/util/cookie.go
+ - https://golang.org/src/net/http/cookie.go
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (Secure\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages:
+ - go
+
+ - id: go.lang.security.audit.net.cookie-missing-httponly.cookie-missing-httponly
+ patterns:
+ - pattern-not-inside: |
+ http.Cookie{
+ ...,
+ HttpOnly: true,
+ ...,
+ }
+ - pattern: |
+ http.Cookie{
+ ...,
+ }
+ message: A session cookie was detected without setting the 'HttpOnly' flag. The
+ 'HttpOnly' flag for cookies instructs the browser to forbid client-side
+ scripts from reading the cookie which mitigates XSS attacks. Set the
+ 'HttpOnly' flag by setting 'HttpOnly' to 'true' in the Cookie.
+ metadata:
+ cwe:
+ - "CWE-1004: Sensitive Cookie Without 'HttpOnly' Flag"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://github.com/0c34/govwa/blob/139693e56406b5684d2a6ae22c0af90717e149b8/util/cookie.go
+ - https://golang.org/src/net/http/cookie.go
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ subcategory:
+ - vuln
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (HttpOnly\s*:\s+)false
+ replacement: \1true
+ severity: WARNING
+ languages:
+ - go
+
+ - id: session-cookie-samesitenone
+ patterns:
+ - pattern-inside: |
+ &sessions.Options{
+ ...,
+ SameSite: http.SameSiteNoneMode,
+ ...,
+ }
+ - pattern: |
+ &sessions.Options{
+ ...,
+ }
+ message: Found SameSiteNoneMode setting in Gorilla session options. Consider
+ setting SameSite to Lax, Strict or Default for enhanced security.
+ metadata:
+ cwe:
+ - "CWE-1275: Sensitive Cookie with Improper SameSite Attribute"
+ owasp:
+ - A05:2021 - Security Misconfiguration
+ references:
+ - https://pkg.go.dev/github.com/gorilla/sessions#Options
+ category: security
+ technology:
+ - gorilla
+ confidence: MEDIUM
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cookie Security
+ fix-regex:
+ regex: (SameSite\s*:\s+)http.SameSiteNoneMode
+ replacement: \1http.SameSiteDefaultMode
+ severity: WARNING
+ languages:
+ - go
+
+ - id: use-of-md5
+ message: Detected MD5 hash algorithm which is considered insecure. MD5 is not
+ collision resistant and is therefore not suitable as a cryptographic
+ signature. Use SHA256 or SHA3 instead.
+ languages:
+ - go
+ severity: WARNING
+ metadata:
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ cwe:
+ - "CWE-328: Use of Weak Hash"
+ source-rule-url: https://github.com/securego/gosec#available-rules
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ references:
+ - https://owasp.org/Top10/A02_2021-Cryptographic_Failures
+ subcategory:
+ - vuln
+ likelihood: MEDIUM
+ impact: MEDIUM
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Insecure Hashing Algorithm
+ patterns:
+ - pattern-inside: |
+ import "crypto/md5"
+ ...
+ - pattern-either:
+ - pattern: |
+ md5.New()
+ - pattern: |
+ md5.Sum(...)
+
+ - id: formatted-template-string
+ message: Found a formatted template string passed to 'template.HTML()'.
+ 'template.HTML()' does not escape contents. Be absolutely sure there is no
+ user-controlled data in this template. If user data can reach this
+ template, you may have a XSS vulnerability.
+ metadata:
+ cwe:
+ - "CWE-79: Improper Neutralization of Input During Web Page Generation
+ ('Cross-site Scripting')"
+ owasp:
+ - A07:2017 - Cross-Site Scripting (XSS)
+ - A03:2021 - Injection
+ references:
+ - https://golang.org/pkg/html/template/#HTML
+ category: security
+ technology:
+ - go
+ confidence: MEDIUM
+ cwe2022-top25: true
+ cwe2021-top25: true
+ subcategory:
+ - audit
+ likelihood: LOW
+ impact: MEDIUM
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Cross-Site-Scripting (XSS)
+ languages:
+ - go
+ severity: WARNING
+ patterns:
+ - pattern-not: template.HTML("..." + "...")
+ - pattern-either:
+ - pattern: template.HTML($T + $X, ...)
+ - pattern: template.HTML(fmt.$P("...", ...), ...)
+ - pattern: |
+ $T = "..."
+ ...
+ $T = $FXN(..., $T, ...)
+ ...
+ template.HTML($T, ...)
+ - pattern: |
+ $T = fmt.$P("...", ...)
+ ...
+ template.HTML($T, ...)
+ - pattern: |
+ $T, $ERR = fmt.$P("...", ...)
+ ...
+ template.HTML($T, ...)
+ - pattern: |
+ $T = $X + $Y
+ ...
+ template.HTML($T, ...)
+ - pattern: |-
+ $T = "..."
+ ...
+ $OTHER, $ERR = fmt.$P(..., $T, ...)
+ ...
+ template.HTML($OTHER, ...)
diff --git a/tmp/rules/owasp/plaintext-http-link.yaml b/tmp/rules/owasp/plaintext-http-link.yaml
new file mode 100644
index 00000000..48e50365
--- /dev/null
+++ b/tmp/rules/owasp/plaintext-http-link.yaml
@@ -0,0 +1,33 @@
+rules:
+ - id: plaintext-http-link
+ metadata:
+ category: security
+ technology:
+ - html
+ cwe:
+ - "CWE-319: Cleartext Transmission of Sensitive Information"
+ owasp:
+ - A03:2017 - Sensitive Data Exposure
+ - A02:2021 - Cryptographic Failures
+ confidence: HIGH
+ subcategory:
+ - vuln
+ references:
+ - https://cwe.mitre.org/data/definitions/319.html
+ likelihood: LOW
+ impact: LOW
+ license: Semgrep Rules License v1.0. For more details, visit
+ semgrep.dev/legal/rules-license
+ vulnerability_class:
+ - Mishandled Sensitive Information
+ patterns:
+ - pattern: ...
+ - metavariable-regex:
+ metavariable: $URL
+ regex: ^(?i)http://
+ message: This link points to a plaintext HTTP URL. Prefer an encrypted HTTPS URL
+ if possible.
+ severity: WARNING
+ languages:
+ - html
+