Admin Messages CSV: defuse spreadsheet formula injection
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 33s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m15s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 33s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m15s
The sender name and message body are user-controlled; a leading =, +, -, @, tab or CR in the CSV export would execute as a formula when a moderator opens it in a spreadsheet. csvSafe() prefixes such values with a single quote. Unit-tested.
This commit is contained in:
@@ -213,13 +213,29 @@ func (s *Server) consoleMessagesCSV(c *gin.Context) {
|
||||
w := csv.NewWriter(c.Writer)
|
||||
_ = w.Write([]string{"time", "source", "sender_id", "sender", "ip", "message", "game_id"})
|
||||
for _, m := range items {
|
||||
// The sender name and message body are user-controlled; defuse spreadsheet formula
|
||||
// injection so a moderator opening the export can't trigger a formula.
|
||||
_ = w.Write([]string{
|
||||
fmtTime(m.CreatedAt), m.Source, m.SenderID.String(), m.SenderName, m.SenderIP, m.Body, m.GameID.String(),
|
||||
fmtTime(m.CreatedAt), m.Source, m.SenderID.String(), csvSafe(m.SenderName), csvSafe(m.SenderIP), csvSafe(m.Body), m.GameID.String(),
|
||||
})
|
||||
}
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
// csvSafe defuses CSV/spreadsheet formula injection: a value a spreadsheet would treat as a
|
||||
// formula (a leading =, +, -, @, tab or CR) is prefixed with a single quote so it renders as
|
||||
// plain text on open.
|
||||
func csvSafe(s string) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
switch s[0] {
|
||||
case '=', '+', '-', '@', '\t', '\r':
|
||||
return "'" + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// consoleUserDetail renders one account with its stats, identities and games.
|
||||
func (s *Server) consoleUserDetail(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
Reference in New Issue
Block a user