Specifically for PHP SQL injection, you can use PDO's prepared statements, and for shell commands you have https://www.php.net/manual/en/function.escapeshellcmd.php and https://www.php.net/manual/en/function.escapeshellarg.php
subprocess.run_escaped("tac -- %s | grep 1 > %s", "foo.txt", "bar.txt")
over this: with open("bar.txt", "w") as outfile:
tac = subprocess.Popen(["tac", "--", "foo.txt"], stdout=subprocess.PIPE)
grep = subprocess.Popen(["grep", "1"], stdin=tac.stdout, stdout=outfile)
tac.wait()
grep.wait()
(In practice the shell grammar is so grotesque that I'd never trust it to be 100% correct. But the idea is nice.)Plus if you’re going to those lengths then you’re better off embedding a Lua/Python/Perl/whatever interpreter in your application.
From the article:
> I would have written this in Rust, but, alas, it’s not vulnerable to this particular attack :)
But of course, strictly speaking, this isn't the case, as you can always call sh -c and rust doest prevent you from doing that (though makes it harder).
Whereas standard libraries that silently fork sh are less safer because they introduce the shell injection vulnerability by default thus requiring developers to consciously be aware of the need to sanitise their inputs.
As I've mentioned elsewhere, I personally think it was a boneheaded decision to ever make exec() functions call a shell. Maybe I'll forgive Perl because that has it's origins as being an extension to shell scripting. But by the time node.js was released people really should have known better.
While that's true, some language's standard libraries don't expose this vulnerability so the developer has to explicitly write bad code. And that's what really matters.
Take node.js for example: it's not going to be obvious that `exec(command)` is actually equivalent to `sh -c "$command"` and thus a lot of people will get caught short if they have unescaped shell script tokens (eg $, <, >, ;, etc) in their command string. And that's very easy to happen.
Personally I think it was a boneheaded decision when most of the languages adopted that approach of forking to sh. If developers wanted to fork to a shell then they could still explicitly write the code to do so (like the `spawn("/bin/sh", "-c", cmd)` example in the article). But by having exec fork sh by default you're creating a risk for all sorts of unexpected behaviour from developers who don't know their language's standard library inside out (which, lets be honest, is going to be most developers given the point of standard libraries is to abstract away that complexity so developers don't need to think about it).
This isn't just a web scale problem either. In fact the example given in that article is a perfect one because its a CI/CD pipeline running trusted input that still failed unexpectedly.