Skip to content

Commit b572614

Browse files
oliwerGopher Bot
authored andcommitted
BUG/MEDIUM: runtime: reject special characters in filenames
Avoid command injection when calling the runtime API by rejecting dangerous characters which should never be used in filenames. The rejected characters: \r\n<>*;$#&{}" Note that we cannot simply add quotes around arguments in runtime commands since HAProxy does not parse them, unlike in the configuration file.
1 parent 6065be8 commit b572614

16 files changed

+118
-0
lines changed

runtime/runtime_single_client.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const (
3333
masterSocket socketType = "master"
3434
)
3535

36+
var ErrRuntimeInvalidChar = errors.New("invalid character found in runtime command")
37+
3638
type socketType string
3739

3840
// SingleRuntime handles one runtime API
@@ -180,6 +182,10 @@ func (s *SingleRuntime) ExecuteMaster(command string) (string, error) {
180182
}
181183

182184
func (s *SingleRuntime) executeRaw(command string, retry int, socket socketType) (string, error) {
185+
// Make sure to only execute a single command.
186+
if strings.ContainsAny(command, ";") {
187+
return "", ErrRuntimeInvalidChar
188+
}
183189
result, err := s.readFromSocket(command, socket)
184190
if err != nil && retry > 0 {
185191
retry--
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2026 HAProxy Technologies
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
package runtime_test
17+
18+
import (
19+
"errors"
20+
"testing"
21+
22+
"github.com/haproxytech/client-native/v6/runtime"
23+
)
24+
25+
func TestSingleRuntime_Execute(t *testing.T) {
26+
tests := []struct {
27+
name string // description of this test case
28+
command string
29+
wantErr error
30+
}{
31+
{
32+
name: "two commands with semicolon",
33+
command: "show ssl cert foo;dump ssl foo",
34+
wantErr: runtime.ErrRuntimeInvalidChar,
35+
},
36+
}
37+
for _, tt := range tests {
38+
t.Run(tt.name, func(t *testing.T) {
39+
var s runtime.SingleRuntime
40+
gotErr := s.Execute(tt.command)
41+
if !errors.Is(gotErr, tt.wantErr) {
42+
t.Errorf("got %v, expected %v", gotErr, tt.wantErr)
43+
}
44+
})
45+
}
46+
}

specification/build/haproxy_spec.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12036,6 +12036,7 @@ parameters:
1203612036
description: Parent name
1203712037
required: true
1203812038
type: string
12039+
pattern: '^[^\r\n&lt;>*;$#&{}"]+$'
1203912040
full_section:
1204012041
name: full_section
1204112042
in: query
@@ -25429,6 +25430,7 @@ paths:
2542925430
- description: ACL file entry ID
2543025431
in: path
2543125432
name: id
25433+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2543225434
required: true
2543325435
type: string
2543425436
produces:
@@ -25521,6 +25523,7 @@ paths:
2552125523
- description: File entry ID
2552225524
in: path
2552325525
name: id
25526+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2552425527
required: true
2552525528
type: string
2552625529
produces:
@@ -25544,6 +25547,7 @@ paths:
2554425547
- description: File entry ID
2554525548
in: path
2554625549
name: id
25550+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2554725551
required: true
2554825552
type: string
2554925553
produces:
@@ -25628,6 +25632,7 @@ paths:
2562825632
- description: Server name
2562925633
in: path
2563025634
name: name
25635+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2563125636
required: true
2563225637
type: string
2563325638
- $ref: '#/parameters/parent_name'
@@ -25650,6 +25655,7 @@ paths:
2565025655
- description: Server name
2565125656
in: path
2565225657
name: name
25658+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2565325659
required: true
2565425660
type: string
2565525661
- $ref: '#/parameters/parent_name'
@@ -25672,6 +25678,7 @@ paths:
2567225678
- description: Server name
2567325679
in: path
2567425680
name: name
25681+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2567525682
required: true
2567625683
type: string
2567725684
- $ref: '#/parameters/parent_name'
@@ -25744,6 +25751,7 @@ paths:
2574425751
- description: SSL CA file name
2574525752
in: path
2574625753
name: name
25754+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2574725755
required: true
2574825756
type: string
2574925757
responses:
@@ -25763,6 +25771,7 @@ paths:
2576325771
- description: SSL CA file name
2576425772
in: path
2576525773
name: name
25774+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2576625775
required: true
2576725776
type: string
2576825777
produces:
@@ -25788,6 +25797,7 @@ paths:
2578825797
- description: SSL CA file name
2578925798
in: path
2579025799
name: name
25800+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2579125801
required: true
2579225802
type: string
2579325803
- in: formData
@@ -25815,6 +25825,7 @@ paths:
2581525825
- description: SSL CA file name
2581625826
in: path
2581725827
name: name
25828+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2581825829
required: true
2581925830
type: string
2582025831
- description: Payload of the cert entry
@@ -25845,6 +25856,7 @@ paths:
2584525856
- description: SSL CA file name
2584625857
in: path
2584725858
name: name
25859+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2584825860
required: true
2584925861
type: string
2585025862
- description: SSL CA file index
@@ -25917,6 +25929,7 @@ paths:
2591725929
- description: SSL certificate name
2591825930
in: path
2591925931
name: name
25932+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2592025933
required: true
2592125934
type: string
2592225935
responses:
@@ -25936,6 +25949,7 @@ paths:
2593625949
- description: SSL certificate name
2593725950
in: path
2593825951
name: name
25952+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2593925953
required: true
2594025954
type: string
2594125955
produces:
@@ -25959,6 +25973,7 @@ paths:
2595925973
- description: SSL certificate name
2596025974
in: path
2596125975
name: name
25976+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2596225977
required: true
2596325978
type: string
2596425979
- in: formData
@@ -26026,6 +26041,7 @@ paths:
2602626041
- description: CRL file name
2602726042
in: path
2602826043
name: name
26044+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2602926045
required: true
2603026046
type: string
2603126047
responses:
@@ -26045,6 +26061,7 @@ paths:
2604526061
- description: CRL file name
2604626062
in: path
2604726063
name: name
26064+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2604826065
required: true
2604926066
type: string
2605026067
- description: Entry index to return. Starts at 1. If not provided, all entries are returned.
@@ -26076,6 +26093,7 @@ paths:
2607626093
- description: CRL file name
2607726094
in: path
2607826095
name: name
26096+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2607926097
required: true
2608026098
type: string
2608126099
- description: CRL file contents
@@ -26118,11 +26136,13 @@ paths:
2611826136
- description: SSL crt list name
2611926137
in: query
2612026138
name: name
26139+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2612126140
required: true
2612226141
type: string
2612326142
- description: SSL cert entry name
2612426143
in: query
2612526144
name: cert_file
26145+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2612626146
required: true
2612726147
type: string
2612826148
- description: The line number where the entry is located, in case several entries share the same certificate.
@@ -26150,6 +26170,7 @@ paths:
2615026170
- description: SSL crt-list filename
2615126171
in: query
2615226172
name: name
26173+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2615326174
required: true
2615426175
type: string
2615526176
produces:
@@ -26171,6 +26192,7 @@ paths:
2617126192
- description: SSL crt-list filename
2617226193
in: query
2617326194
name: name
26195+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2617426196
required: true
2617526197
type: string
2617626198
- in: body
@@ -26214,6 +26236,7 @@ paths:
2621426236
- description: Stick table name
2621526237
in: path
2621626238
name: name
26239+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2621726240
required: true
2621826241
type: string
2621926242
responses:
@@ -26237,10 +26260,12 @@ paths:
2623726260
- description: A list of filters in format data.<type> <operator> <value> separated by comma
2623826261
in: query
2623926262
name: filter
26263+
pattern: ^[^\r\n;#]+$
2624026264
type: string
2624126265
- description: Key which we want the entries for
2624226266
in: query
2624326267
name: key
26268+
pattern: ^[^\r\n;#]+$
2624426269
type: string
2624526270
- description: Max number of entries to be returned for pagination
2624626271
in: query
@@ -26273,6 +26298,7 @@ paths:
2627326298
data_type:
2627426299
$ref: '#/definitions/stick_table_entry'
2627526300
key:
26301+
pattern: ^[^\r\n;#]+$
2627626302
type: string
2627726303
required:
2627826304
- key
@@ -26317,6 +26343,7 @@ paths:
2631726343
- description: Map file name
2631826344
in: path
2631926345
name: name
26346+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2632026347
required: true
2632126348
type: string
2632226349
- description: If true, deletes file from disk
@@ -26345,6 +26372,7 @@ paths:
2634526372
- description: Map file name
2634626373
in: path
2634726374
name: name
26375+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2634826376
required: true
2634926377
type: string
2635026378
responses:
@@ -26366,6 +26394,7 @@ paths:
2636626394
- description: Map file name
2636726395
in: path
2636826396
name: name
26397+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2636926398
required: true
2637026399
type: string
2637126400
- default: false
@@ -26445,6 +26474,7 @@ paths:
2644526474
- description: Map id
2644626475
in: path
2644726476
name: id
26477+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2644826478
required: true
2644926479
type: string
2645026480
- $ref: '#/parameters/parent_name'
@@ -26470,6 +26500,7 @@ paths:
2647026500
- description: Map id
2647126501
in: path
2647226502
name: id
26503+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2647326504
required: true
2647426505
type: string
2647526506
- $ref: '#/parameters/parent_name'
@@ -26492,6 +26523,7 @@ paths:
2649226523
- description: Map id
2649326524
in: path
2649426525
name: id
26526+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2649526527
required: true
2649626528
type: string
2649726529
- $ref: '#/parameters/parent_name'
@@ -26548,6 +26580,7 @@ paths:
2654826580
- description: Certificate file name
2654926581
in: query
2655026582
name: certificate
26583+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2655126584
required: true
2655226585
type: string
2655326586
responses:

specification/haproxy-spec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ parameters:
830830
description: Parent name
831831
required: true
832832
type: string
833+
pattern: '^[^\r\n<>*;$#&{}"]+$'
833834
full_section:
834835
name: full_section
835836
in: query

specification/paths/runtime/acls.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ acls_one:
2828
description: ACL file entry ID
2929
required: true
3030
type: string
31+
pattern: '^[^\r\n<>*;$#&{}"]+$'
3132
responses:
3233
'200':
3334
description: Successful operation

specification/paths/runtime/acls_entries.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ acls_entries_one:
1414
description: File entry ID
1515
required: true
1616
type: string
17+
pattern: '^[^\r\n<>*;$#&{}"]+$'
1718
responses:
1819
'200':
1920
description: Successful operation
@@ -39,6 +40,7 @@ acls_entries_one:
3940
description: File entry ID
4041
required: true
4142
type: string
43+
pattern: '^[^\r\n<>*;$#&{}"]+$'
4244
responses:
4345
'204':
4446
description: Successful operation

specification/paths/runtime/acme.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ acme:
2626
description: Certificate file name
2727
required: true
2828
type: string
29+
pattern: '^[^\r\n<>*;$#&{}"]+$'
2930
responses:
3031
'200':
3132
description: Operation started

specification/paths/runtime/maps.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ maps_one:
3535
description: Map file name
3636
required: true
3737
type: string
38+
pattern: '^[^\r\n<>*;$#&{}"]+$'
3839
responses:
3940
'200':
4041
description: Successful operation
@@ -54,6 +55,7 @@ maps_one:
5455
description: Map file name
5556
required: true
5657
type: string
58+
pattern: '^[^\r\n<>*;$#&{}"]+$'
5759
- name: forceDelete
5860
in: query
5961
description: If true, deletes file from disk
@@ -82,6 +84,7 @@ maps_one:
8284
description: Map file name
8385
required: true
8486
type: string
87+
pattern: '^[^\r\n<>*;$#&{}"]+$'
8588
- name: force_sync
8689
in: query
8790
description: If true, immediately syncs changes to disk

0 commit comments

Comments
 (0)