Skip to content

Commit 1b1bf28

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 c71b199 commit 1b1bf28

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
@@ -12197,6 +12197,7 @@ parameters:
1219712197
description: Parent name
1219812198
required: true
1219912199
type: string
12200+
pattern: '^[^\r\n&lt;>*;$#&{}"]+$'
1220012201
full_section:
1220112202
name: full_section
1220212203
in: query
@@ -25590,6 +25591,7 @@ paths:
2559025591
- description: ACL file entry ID
2559125592
in: path
2559225593
name: id
25594+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2559325595
required: true
2559425596
type: string
2559525597
produces:
@@ -25682,6 +25684,7 @@ paths:
2568225684
- description: File entry ID
2568325685
in: path
2568425686
name: id
25687+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2568525688
required: true
2568625689
type: string
2568725690
produces:
@@ -25705,6 +25708,7 @@ paths:
2570525708
- description: File entry ID
2570625709
in: path
2570725710
name: id
25711+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2570825712
required: true
2570925713
type: string
2571025714
produces:
@@ -25789,6 +25793,7 @@ paths:
2578925793
- description: Server name
2579025794
in: path
2579125795
name: name
25796+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2579225797
required: true
2579325798
type: string
2579425799
- $ref: '#/parameters/parent_name'
@@ -25811,6 +25816,7 @@ paths:
2581125816
- description: Server name
2581225817
in: path
2581325818
name: name
25819+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2581425820
required: true
2581525821
type: string
2581625822
- $ref: '#/parameters/parent_name'
@@ -25833,6 +25839,7 @@ paths:
2583325839
- description: Server name
2583425840
in: path
2583525841
name: name
25842+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2583625843
required: true
2583725844
type: string
2583825845
- $ref: '#/parameters/parent_name'
@@ -25905,6 +25912,7 @@ paths:
2590525912
- description: SSL CA file name
2590625913
in: path
2590725914
name: name
25915+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2590825916
required: true
2590925917
type: string
2591025918
responses:
@@ -25924,6 +25932,7 @@ paths:
2592425932
- description: SSL CA file name
2592525933
in: path
2592625934
name: name
25935+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2592725936
required: true
2592825937
type: string
2592925938
produces:
@@ -25949,6 +25958,7 @@ paths:
2594925958
- description: SSL CA file name
2595025959
in: path
2595125960
name: name
25961+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2595225962
required: true
2595325963
type: string
2595425964
- in: formData
@@ -25976,6 +25986,7 @@ paths:
2597625986
- description: SSL CA file name
2597725987
in: path
2597825988
name: name
25989+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2597925990
required: true
2598025991
type: string
2598125992
- description: Payload of the cert entry
@@ -26006,6 +26017,7 @@ paths:
2600626017
- description: SSL CA file name
2600726018
in: path
2600826019
name: name
26020+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2600926021
required: true
2601026022
type: string
2601126023
- description: SSL CA file index
@@ -26078,6 +26090,7 @@ paths:
2607826090
- description: SSL certificate name
2607926091
in: path
2608026092
name: name
26093+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2608126094
required: true
2608226095
type: string
2608326096
responses:
@@ -26097,6 +26110,7 @@ paths:
2609726110
- description: SSL certificate name
2609826111
in: path
2609926112
name: name
26113+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2610026114
required: true
2610126115
type: string
2610226116
produces:
@@ -26120,6 +26134,7 @@ paths:
2612026134
- description: SSL certificate name
2612126135
in: path
2612226136
name: name
26137+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2612326138
required: true
2612426139
type: string
2612526140
- in: formData
@@ -26187,6 +26202,7 @@ paths:
2618726202
- description: CRL file name
2618826203
in: path
2618926204
name: name
26205+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2619026206
required: true
2619126207
type: string
2619226208
responses:
@@ -26206,6 +26222,7 @@ paths:
2620626222
- description: CRL file name
2620726223
in: path
2620826224
name: name
26225+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2620926226
required: true
2621026227
type: string
2621126228
- description: Entry index to return. Starts at 1. If not provided, all entries are returned.
@@ -26237,6 +26254,7 @@ paths:
2623726254
- description: CRL file name
2623826255
in: path
2623926256
name: name
26257+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2624026258
required: true
2624126259
type: string
2624226260
- description: CRL file contents
@@ -26279,11 +26297,13 @@ paths:
2627926297
- description: SSL crt list name
2628026298
in: query
2628126299
name: name
26300+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2628226301
required: true
2628326302
type: string
2628426303
- description: SSL cert entry name
2628526304
in: query
2628626305
name: cert_file
26306+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2628726307
required: true
2628826308
type: string
2628926309
- description: The line number where the entry is located, in case several entries share the same certificate.
@@ -26311,6 +26331,7 @@ paths:
2631126331
- description: SSL crt-list filename
2631226332
in: query
2631326333
name: name
26334+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2631426335
required: true
2631526336
type: string
2631626337
produces:
@@ -26332,6 +26353,7 @@ paths:
2633226353
- description: SSL crt-list filename
2633326354
in: query
2633426355
name: name
26356+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2633526357
required: true
2633626358
type: string
2633726359
- in: body
@@ -26375,6 +26397,7 @@ paths:
2637526397
- description: Stick table name
2637626398
in: path
2637726399
name: name
26400+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2637826401
required: true
2637926402
type: string
2638026403
responses:
@@ -26398,10 +26421,12 @@ paths:
2639826421
- description: A list of filters in format data.<type> <operator> <value> separated by comma
2639926422
in: query
2640026423
name: filter
26424+
pattern: ^[^\r\n;#]+$
2640126425
type: string
2640226426
- description: Key which we want the entries for
2640326427
in: query
2640426428
name: key
26429+
pattern: ^[^\r\n;#]+$
2640526430
type: string
2640626431
- description: Max number of entries to be returned for pagination
2640726432
in: query
@@ -26434,6 +26459,7 @@ paths:
2643426459
data_type:
2643526460
$ref: '#/definitions/stick_table_entry'
2643626461
key:
26462+
pattern: ^[^\r\n;#]+$
2643726463
type: string
2643826464
required:
2643926465
- key
@@ -26478,6 +26504,7 @@ paths:
2647826504
- description: Map file name
2647926505
in: path
2648026506
name: name
26507+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2648126508
required: true
2648226509
type: string
2648326510
- description: If true, deletes file from disk
@@ -26506,6 +26533,7 @@ paths:
2650626533
- description: Map file name
2650726534
in: path
2650826535
name: name
26536+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2650926537
required: true
2651026538
type: string
2651126539
responses:
@@ -26527,6 +26555,7 @@ paths:
2652726555
- description: Map file name
2652826556
in: path
2652926557
name: name
26558+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2653026559
required: true
2653126560
type: string
2653226561
- default: false
@@ -26606,6 +26635,7 @@ paths:
2660626635
- description: Map id
2660726636
in: path
2660826637
name: id
26638+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2660926639
required: true
2661026640
type: string
2661126641
- $ref: '#/parameters/parent_name'
@@ -26631,6 +26661,7 @@ paths:
2663126661
- description: Map id
2663226662
in: path
2663326663
name: id
26664+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2663426665
required: true
2663526666
type: string
2663626667
- $ref: '#/parameters/parent_name'
@@ -26653,6 +26684,7 @@ paths:
2665326684
- description: Map id
2665426685
in: path
2665526686
name: id
26687+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2665626688
required: true
2665726689
type: string
2665826690
- $ref: '#/parameters/parent_name'
@@ -26709,6 +26741,7 @@ paths:
2670926741
- description: Certificate file name
2671026742
in: query
2671126743
name: certificate
26744+
pattern: ^[^\r\n&lt;>*;$#&{}"]+$
2671226745
required: true
2671326746
type: string
2671426747
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)