Skip to content

Commit 876b7e2

Browse files
committed
Add Apache-style CGI query escaping and STRICT_CGI_PARAMS whitelist; update compile.cmd to use PP env var and standard 7-Zip path; update copyright to 2025
1 parent c9e2979 commit 876b7e2

File tree

10 files changed

+207
-42
lines changed

10 files changed

+207
-42
lines changed

.dockerignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.git
2+
.gitignore
3+
*.exe
4+
*.obj
5+
*.o
6+
*.dcu
7+
*.bak
8+
*.7z
9+
*.log
10+
*.md
11+
Dockerfile
12+
Dockerfile.old
13+
Dockerfile.new

Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM debian:stable-slim
2+
RUN apt-get update && apt-get install -y mingw-w64 && rm -rf /var/lib/apt/lists/*
3+
COPY CGITEST/login.c /app/login.c
4+
WORKDIR /app
5+
RUN x86_64-w64-mingw32-gcc -o login.exe login.c

SRC/SrvMain.pas

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//////////////////////////////////////////////////////////////////////////
22
//
33
// TinyWeb
4-
// Copyright (C) 2021-2023 Maxim Masiutin
4+
// Copyright (C) 2021-2025 Maxim Masiutin
55
// Copyright (C) 2000-2017 RITLABS S.R.L.
66
// Copyright (C) 1997-2000 RIT Research Labs
77
//
@@ -796,6 +796,71 @@ procedure TPipeReadStdThread.Execute;
796796
until False;
797797
end;
798798

799+
// CGI Query Parameter Security Functions
800+
// Implements defense-in-depth for ISINDEX-style queries per RFC 3875 Section 4.4
801+
// Reference: https://datatracker.ietf.org/doc/html/rfc3875#section-4.4
802+
//
803+
// Two layers of protection:
804+
// 1. Whitelist validation (optional, enabled by STRICT_CGI_PARAMS define)
805+
// 2. Apache-style shell metacharacter escaping (always active)
806+
807+
{$IFDEF STRICT_CGI_PARAMS}
808+
// Whitelist validation: Only allow safe characters in CGI query parameters
809+
// Returns True if the parameter contains only safe characters
810+
// This is the first layer - rejects requests with unsafe characters
811+
function IsQueryParamSafe(const s: AnsiString): Boolean;
812+
var
813+
i: Integer;
814+
c: AnsiChar;
815+
begin
816+
Result := True;
817+
for i := 1 to Length(s) do
818+
begin
819+
c := s[i];
820+
// Allow: A-Z, a-z, 0-9, hyphen, underscore, dot, forward slash, backslash, colon
821+
if not ((c >= 'A') and (c <= 'Z')) and
822+
not ((c >= 'a') and (c <= 'z')) and
823+
not ((c >= '0') and (c <= '9')) and
824+
(c <> '-') and (c <> '_') and (c <> '.') and
825+
(c <> '/') and (c <> '\') and (c <> ':') then
826+
begin
827+
Result := False;
828+
Exit;
829+
end;
830+
end;
831+
end;
832+
{$ENDIF}
833+
834+
// Apache-style shell metacharacter escaping for Windows
835+
// Escapes dangerous characters with caret (^) and wraps in quotes
836+
// Based on Apache httpd ap_escape_shell_cmd() and PHP escapeshellcmd()
837+
// This is the second layer - always active as defense-in-depth
838+
// Reference: https://datatracker.ietf.org/doc/html/rfc3875#section-7.2
839+
function EscapeShellParam(const s: AnsiString): AnsiString;
840+
const
841+
// Windows shell metacharacters that need escaping
842+
DangerousChars = '&|<>^()%!"''`;$[]{}*?~';
843+
var
844+
i: Integer;
845+
c: AnsiChar;
846+
begin
847+
Result := '';
848+
for i := 1 to Length(s) do
849+
begin
850+
c := s[i];
851+
// Block newlines entirely - cannot be safely escaped on Windows
852+
if (c = #10) or (c = #13) then
853+
Continue;
854+
// Escape dangerous characters with caret (Windows cmd.exe escape char)
855+
if Pos(c, DangerousChars) > 0 then
856+
Result := Result + '^';
857+
Result := Result + c;
858+
end;
859+
// Wrap in quotes for additional safety
860+
if Result <> '' then
861+
Result := '"' + Result + '"';
862+
end;
863+
799864
function ExecuteScript(const AExecutable, APath, AScript, AQueryParam, AEnvStr,
800865
AStdInStr: AnsiString; Buffer: THTTPServerThreadBuffer; SelfThr: TThread;
801866
var ErrorMsg: AnsiString): TEntityHeader;
@@ -852,8 +917,9 @@ function ExecuteScript(const AExecutable, APath, AScript, AQueryParam, AEnvStr,
852917
s := AExecutable
853918
else
854919
s := AExecutable + ' ' + AScript;
920+
// Apply Apache-style escaping to query parameter (always active)
855921
if AQueryParam <> '' then
856-
s := s + ' ' + AQueryParam;
922+
s := s + ' ' + EscapeShellParam(AQueryParam);
857923
s := DelSpaces(s);
858924
if (s = '') or (AEnvStr = '') or (APath = '') then
859925
begin
@@ -1921,12 +1987,21 @@ procedure THTTPServerThread.Execute;
19211987
CEqual := '=';
19221988
if Pos(CEqual, URIQuery) = 0 then
19231989
begin
1990+
// ISINDEX-style query (no '=' sign) per RFC 3875 Section 4.4
19241991
URIQueryParam := URIQuery;
19251992
if not UnpackPchars(URIQueryParam) then
19261993
Break;
19271994
CZero := #0;
19281995
if Pos(CZero, URIQueryParam) > 0 then
19291996
Break;
1997+
{$IFDEF STRICT_CGI_PARAMS}
1998+
// Whitelist validation: reject parameters with unsafe characters
1999+
if not IsQueryParamSafe(URIQueryParam) then
2000+
begin
2001+
StatusCode := 400;
2002+
Break;
2003+
end;
2004+
{$ENDIF}
19302005
end;
19312006
end;
19322007
CSemicolon := ';';

SRC/Tiny.dpr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//////////////////////////////////////////////////////////////////////////
22
//
33
// TinyWeb
4-
// Copyright (C) 2021-2023 Maxim Masiutin
4+
// Copyright (C) 2021-2025 Maxim Masiutin
55
// Copyright (C) 2000-2017 RITLABS S.R.L.
66
// Copyright (C) 1997-2000 RIT Research Labs
77
//

SRC/compile.cmd

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
1-
@echo off
2-
if exist TinyWeb.7z del TinyWeb.7z
3-
if exist Tiny.exe del Tiny.exe
4-
C:\FPC\3.2.2\bin\i386-win32\fpc.exe -OpCOREAVX2 -O4 -Pi386 -Twin32 -B -MObjFPC Tiny.dpr
5-
7z a -mx9 TinyWeb Tiny.exe
1+
@echo off
2+
REM PP - path to Free Pascal compiler (standard FPC Makefile variable)
3+
REM If not set, uses fpc.exe from PATH
4+
5+
if exist TinyWeb.7z del TinyWeb.7z
6+
if exist Tiny.exe del Tiny.exe
7+
8+
if defined PP (
9+
"%PP%" -OpCOREAVX2 -O4 -Pi386 -Twin32 -B -MObjFPC Tiny.dpr
10+
) else (
11+
fpc.exe -OpCOREAVX2 -O4 -Pi386 -Twin32 -B -MObjFPC Tiny.dpr
12+
)
13+
14+
if exist "%ProgramFiles%\7-Zip\7z.exe" (
15+
"%ProgramFiles%\7-Zip\7z.exe" a -mx9 TinyWeb Tiny.exe
16+
) else (
17+
7z.exe a -mx9 TinyWeb Tiny.exe
18+
)

SRC/define.inc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
11
{$DEFINE LOGGING}
2+
3+
// STRICT_CGI_PARAMS: When enabled, CGI query parameters (ISINDEX-style queries
4+
// without '=' per RFC 3875 Section 4.4) are validated against a whitelist of
5+
// safe characters. Parameters containing characters outside [A-Za-z0-9._-/\:]
6+
// will be rejected with HTTP 400 Bad Request.
7+
//
8+
// When disabled, Apache-style shell metacharacter escaping is used instead,
9+
// which allows all characters but escapes dangerous ones. This provides full
10+
// compatibility with legacy CGI scripts that may need special characters.
11+
//
12+
// Default: ENABLED (strictest security)
13+
// To disable: Comment out or remove the line below
14+
{$DEFINE STRICT_CGI_PARAMS}

SRC/xBase.pas

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//////////////////////////////////////////////////////////////////////////
22
//
33
// TinyWeb
4-
// Copyright (C) 2021-2023 Maxim Masiutin
4+
// Copyright (C) 2021-2025 Maxim Masiutin
55
// Copyright (C) 1997-2000 RIT Research Labs
66
// Copyright (C) 2000-2017 RITLABS S.R.L.
77
//

history.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,5 +103,13 @@ <h2>1.97 (11 April 2023)</h2>
103103
<li>The file buffers of all log files are flushed, not just the logs of the access logs</li>
104104
<li>The query string of the GET method is also written to the access log</li>
105105
</ul>
106+
<h2>1.98 (23 November 2025)</h2>
107+
<ul>
108+
<li>Apache-style shell metacharacter escaping for CGI query parameters (always active)</li>
109+
<li>Optional strict whitelist validation via STRICT_CGI_PARAMS define (enabled by default)</li>
110+
<li>CGI query parameters protected by two layers: whitelist rejects unsafe chars, escaping sanitizes the rest</li>
111+
<li>Full ISINDEX query support preserved per <a href="https://datatracker.ietf.org/doc/html/rfc3875#section-4.4">RFC 3875 Section 4.4</a></li>
112+
<li>To allow special characters in queries, disable STRICT_CGI_PARAMS in define.inc</li>
113+
</ul>
106114
</body>
107115
</html>

licence.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
TinyWeb
2-
Copyright (C) 2021-2023 Maxim Masiutin
2+
Copyright (C) 2021-2025 Maxim Masiutin
33
Copyright (C) 2000-2017 RITLABS S.R.L.
44
Copyright (C) 1997-2000 RIT Research Labs
55

readme.md

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,70 @@
1-
# TinyWeb Server
2-
Version 1.97
3-
Released April 11, 2023
4-
Written by Maxim Masiutin
5-
Copyright (C) 2021-2023 Maxim Masiutin
6-
Copyright (C) 2000-2017 RITLABS S.R.L.
7-
Copyright (C) 1997-2000 RIT Research Labs
8-
9-
## Setup
10-
To set up the TinyWeb Server, just create a shortcut in the Startup menu with the following properties:
11-
12-
### Target
13-
`c:\www\bin\tiny.exe c:\www\root`
14-
### Start In
15-
`c:\www\log`
16-
17-
18-
Here, `c:\www\bin\tiny.exe` is the path to TinyWeb executable, `c:\www\root` is the path to www home (root) directory, and `c:\www\log` is the directory for log files that TinyWeb keeps.
19-
20-
TinyWeb is not a windowed application, so there is no window with TinyWeb. It is also not a console application, so there is no console window for TinyWeb. Moreover, it is not a Windows Service. Once started, the `tiny.exe` process will appear in Task List. There is no way to stop Tiny Web except via the "End Task" operation.
21-
22-
## Command-line Options
23-
1. First parameter (mandatory) is a path to the www home (root) directory.
24-
2. Second parameter (optional) is a port number. By default, it is 80 for HTTP and 443 for HTTPS(SSL/TLS).
25-
3. Third parameter (optional) is a dotted-decimal IP address to bind the server. By default, TinyWeb binds to all available local addresses.
26-
27-
## Examples
28-
29-
### Run TinyWeb on port 8000:
30-
`c:\www\bin\tiny.exe c:\www\root 8000`
31-
### Run TinyWeb on port 8000 and address 212.56.194.250:
32-
`c:\www\bin\tiny.exe c:\www\root 8000 212.56.194.250`
1+
# TinyWeb Server
2+
Version 1.97
3+
Released April 11, 2023
4+
Written by Maxim Masiutin
5+
Copyright (C) 2021-2025 Maxim Masiutin
6+
Copyright (C) 2000-2017 RITLABS S.R.L.
7+
Copyright (C) 1997-2000 RIT Research Labs
8+
9+
## Setup
10+
To set up the TinyWeb Server, just create a shortcut in the Startup menu with the following properties:
11+
12+
### Target
13+
`c:\www\bin\tiny.exe c:\www\root`
14+
### Start In
15+
`c:\www\log`
16+
17+
18+
Here, `c:\www\bin\tiny.exe` is the path to TinyWeb executable, `c:\www\root` is the path to www home (root) directory, and `c:\www\log` is the directory for log files that TinyWeb keeps.
19+
20+
**Note:** These paths are just examples. You can customize them to your needs. Also, make sure that an `index.html` file exists in your www home (root) directory, otherwise the server will fail to start.
21+
22+
TinyWeb is not a windowed application, so there is no window with TinyWeb. It is also not a console application, so there is no console window for TinyWeb. Moreover, it is not a Windows Service. Once started, the `tiny.exe` process will appear in Task List. There is no way to stop Tiny Web except via the "End Task" operation.
23+
24+
## Command-line Options
25+
1. First parameter (mandatory) is a path to the www home (root) directory.
26+
2. Second parameter (optional) is a port number. By default, it is 80 for HTTP and 443 for HTTPS(SSL/TLS).
27+
3. Third parameter (optional) is a dotted-decimal IP address to bind the server. By default, TinyWeb binds to all available local addresses.
28+
29+
## Examples
30+
31+
### Run TinyWeb on port 8000:
32+
`c:\www\bin\tiny.exe c:\www\root 8000`
33+
### Run TinyWeb on port 8000 and address 212.56.194.250:
34+
`c:\www\bin\tiny.exe c:\www\root 8000 212.56.194.250`
35+
36+
## Building CGI Executables with Docker
37+
38+
You can use Docker to cross-compile C files into Windows executables using MinGW.
39+
40+
### Build hello.exe
41+
```cmd
42+
docker build -t tinyweb-login-c .
43+
```
44+
45+
### Extract the executable and copy to cgi-bin
46+
```cmd
47+
docker create --name temp tinyweb-login-c
48+
docker cp temp:/app/hello.exe c:\www\root\cgi-bin\hello.exe
49+
docker rm temp
50+
```
51+
52+
The Dockerfile uses `debian:stable-slim` with MinGW to compile `CGITEST/hello.c` into a Windows executable.
53+
54+
## CGI Query Parameter Handling
55+
56+
TinyWeb supports ISINDEX-style queries per [RFC 3875 Section 4.4](https://datatracker.ietf.org/doc/html/rfc3875#section-4.4). Query strings without `=` are passed as command-line arguments to CGI scripts.
57+
58+
### Security
59+
60+
CGI query parameters are protected by two layers:
61+
1. **Whitelist validation** (optional): Rejects parameters with unsafe characters
62+
2. **Apache-style escaping** (always active): Escapes shell metacharacters
63+
64+
### Configuration
65+
66+
The `STRICT_CGI_PARAMS` define in `SRC/define.inc` controls whitelist validation:
67+
- **Enabled (default)**: Only allows `[A-Za-z0-9._-/\:]` in query parameters
68+
- **Disabled**: Allows all characters (escaped for safety)
69+
70+
To disable strict mode, comment out `{$DEFINE STRICT_CGI_PARAMS}` in `define.inc`.

0 commit comments

Comments
 (0)