Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 12:15 UTC
admin: reject non-canonical config array indices (#7592)
* admin: reject non-canonical config array indices * admin: expand canonical array index test coverage * Update admin.go Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * Update admin.go Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * admin: improve canonical array index test diagnostics --------- Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
Amemoyoi committed
May 1, 2026 at 00:39 UTC
18ab0f955fc1075d7727c7658dbfb734c673a5c9
2 files changed
+62
-2
admin.go
+17
-2
@@ -1161,6 +1161,20 @@ func handleStop(w http.ResponseWriter, r *http.Request) error {
1161
return nil
1162
}
1163
1164
+func parseCanonicalArrayIndex(idx string) (int, error) {
1165
+ if idx == "" {
1166
+ return 0, fmt.Errorf("empty index")
1167
+ }
1168
+ i, err := strconv.Atoi(idx)
1169
+ if err != nil {
1170
+ return 0, err
1171
+ }
1172
+ if strconv.Itoa(i) != idx {
1173
+ return 0, fmt.Errorf("non-canonical array index")
1174
+ }
1175
+ return i, nil
1176
+}
1177
+
1178
// unsyncedConfigAccess traverses into the current config and performs
1179
// the operation at path according to method, using body and out as
1180
// needed. This is a low-level, unsynchronized function; most callers
@@ -1222,11 +1236,12 @@ traverseLoop:
1236
var idx int
1237
if method != http.MethodPost {
1238
idxStr := parts[len(parts)-1]
1225
- idx, err = strconv.Atoi(idxStr)
1239
+ idx, err = parseCanonicalArrayIndex(idxStr)
1240
if err != nil {
1241
return fmt.Errorf("[%s] invalid array index '%s': %v",
1242
path, idxStr, err)
1243
}
1244
+
1245
if idx < 0 || (method != http.MethodPut && idx >= len(arr)) || idx > len(arr) {
1246
return fmt.Errorf("[%s] array index out of bounds: %s", path, idxStr)
1247
}
@@ -1326,7 +1341,7 @@ traverseLoop:
1341
}
1342
1343
case []any:
1329
- partInt, err := strconv.Atoi(part)
1344
+ partInt, err := parseCanonicalArrayIndex(part)
1345
if err != nil {
1346
return fmt.Errorf("[/%s] invalid array index '%s': %v",
1347
strings.Join(parts[:i+1], "/"), part, err)
admin_test.go
+45
@@ -15,6 +15,7 @@
15
package caddy
16
17
import (
18
+ "bytes"
19
"context"
20
"crypto"
21
"crypto/tls"
@@ -1106,3 +1107,47 @@ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDRS0LmTwUT0iwP
1107
})
1108
}
1109
}
1110
+
1111
+func TestUnsyncedConfigAccessCanonicalArrayIndices(t *testing.T) {
1112
+ rawCfg = map[string]any{
1113
+ rawConfigKey: map[string]any{
1114
+ "list": []any{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"},
1115
+ },
1116
+ }
1117
+
1118
+ tests := []struct {
1119
+ name string
1120
+ path string
1121
+ wantOutput string
1122
+ wantErr bool
1123
+ }{
1124
+ {name: "allow zero", path: "/" + rawConfigKey + "/list/0", wantOutput: "\"zero\"\n"},
1125
+ {name: "allow one", path: "/" + rawConfigKey + "/list/1", wantOutput: "\"one\"\n"},
1126
+ {name: "allow ten", path: "/" + rawConfigKey + "/list/10", wantOutput: "\"ten\"\n"},
1127
+ {name: "reject leading zero", path: "/" + rawConfigKey + "/list/01", wantErr: true},
1128
+ {name: "reject multiple leading zeros", path: "/" + rawConfigKey + "/list/002", wantErr: true},
1129
+ {name: "reject plus sign", path: "/" + rawConfigKey + "/list/+1", wantErr: true},
1130
+ {name: "reject negative zero", path: "/" + rawConfigKey + "/list/-0", wantErr: true},
1131
+ }
1132
+
1133
+ for i, tc := range tests {
1134
+ t.Run(tc.name, func(t *testing.T) {
1135
+ var gotOutput bytes.Buffer
1136
+ err := unsyncedConfigAccess(http.MethodGet, tc.path, nil, &gotOutput)
1137
+
1138
+ if tc.wantErr {
1139
+ if err == nil {
1140
+ t.Errorf("test %d (%s): input path %q: expected error, got nil with output %q", i, tc.name, tc.path, gotOutput.String())
1141
+ }
1142
+ return
1143
+ }
1144
+
1145
+ if err != nil {
1146
+ t.Errorf("test %d (%s): input path %q: expected no error with output %q, got error %v with output %q", i, tc.name, tc.path, tc.wantOutput, err, gotOutput.String())
1147
+ }
1148
+ if gotOutput.String() != tc.wantOutput {
1149
+ t.Errorf("test %d (%s): input path %q: expected output %q, got %q", i, tc.name, tc.path, tc.wantOutput, gotOutput.String())
1150
+ }
1151
+ })
1152
+ }
1153
+}