Prevention of Regular Expression Denial of Service (ReDoS)
package main
import (
"regexp"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/search", func(c *gin.Context) {
query := c.Query("q")
regex := regexp.MustCompile("^(a+)+$")
if regex.MatchString(query) {
c.JSON(200, gin.H{
"message": "matched",
})
} else {
c.JSON(200, gin.H{
"message": "not matched",
})
}
})
r.Run()
}
The above Go code is vulnerable to a Regular Expression Denial of Service (ReDoS) attack. The vulnerability lies in the regular expression
^(a+)+$
used in the GET request handler for the
/search
endpoint.
This regular expression is problematic because it can lead to catastrophic backtracking when certain strings are evaluated against it. The
+
quantifier means "one or more", and when it is used twice in succession (
a+
and then another
+
), it creates multiple possibilities for the engine to match the input string.
For a string with many 'a' characters, the engine will try all possible permutations to find a match. This can lead to exponential growth in the number of steps the engine has to take, potentially leading to extremely long processing times, exhausting server resources and ultimately causing a denial of service.
If an attacker sends a GET request to the
/search
endpoint with a query string
q
consisting of a large number of 'a' characters, the server could be forced into a state of excessive computation, leading to a denial of service.
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/search", func(c *gin.Context) {
query := c.Query("q")
if len(query) > 100 {
c.JSON(400, gin.H{
"message": "query too long",
})
return
}
if isSimpleString(query) {
c.JSON(200, gin.H{
"message": "matched",
})
} else {
c.JSON(200, gin.H{
"message": "not matched",
})
}
})
r.Run()
}
func isSimpleString(input string) bool {
for _, r := range input {
if r < 'a' || r > 'z' {
return false
}
}
return true
}
The updated code no longer uses a regular expression to match the query string. Instead, it uses a simple function
isSimpleString
to check if the input string only contains lowercase letters. This function iterates over each rune in the string and checks if it is within the range of lowercase letters. If it encounters a rune that is not a lowercase letter, it returns false, indicating that the string does not match.
Before checking the string, the code also checks if the length of the query string is greater than 100. If it is, the code returns a 400 status code with a message indicating that the query is too long. This is a simple form of input validation that can help prevent denial of service attacks by limiting the amount of data that the server needs to process.
This approach avoids the use of complex regular expressions, which can lead to ReDoS vulnerabilities. It also validates and sanitizes user input before using it in string matching, which is a good practice for preventing a variety of security vulnerabilities.