Taint sources
A taint source is a place where untrusted data can enter a program, such as the network, a file or the command line. This page lists every source, what it watches and how to switch it on.
What a source is
A source is a place where data from outside enters your program. When a source is on, EyalSec marks the data arriving through it as untrusted (taint) and follows it. If that data reaches a risky operation, you get an event. A source that is off marks nothing.
Every source is off until you switch it on, so a new machine reports nothing until you choose at least one. Start with the source that matches how your program receives input: socket for a network service, stdin for a filter or pipe, file for something that reads uploaded or downloaded files.
Switching a source on
The usual way to switch a source on is the dashboard: its control in the Taint sources part of the Configure window for one machine, or of the Filters page for every machine. Each control can be On, Off or Unset (see Unset).
es-python also accepts a command-line flag and an environment variable for most sources, which is handy for a single run:
es-python --make-socket-vuln app.py
ES2_MAKE_SOCKET_VULN=1 es-python app.py
A dashboard On or Off always wins over a flag. A flag only counts while the dashboard control is Unset at both the machine and the global level. The full list of flags is on the es-python command line.
A change to a source takes effect the next time the program starts. See When a change reaches the machine.
Which products use which sources
Each product shows only the sources it can use, so the list you see depends on the machine's product. A note under the title of the Configure window or the Filters page says which controls a product does not act on yet.
| Product | Sources shown |
|---|---|
| es-python | socket, file, stdin, foreign, env, make_vuln, argv, weak_random, hardcoded, identity, db_rows, handoff |
| es-chromium | none: it does not fetch machine configuration |
On es-python, the argv, weak_random, hardcoded and identity controls do not change what it reports today. Use the flags described under each of them below instead.
Source names in rules
A source's name in the Taint sources list is not always the name you pick in a rule's Source field. Two sources are spelled differently, and a rule that uses the wrong spelling is rejected.
| In Taint sources | In a rule's Source |
|---|---|
weak_random (underscore) |
weak-random (hyphen) |
db_rows |
db |
Every other source uses the same word in both places. The Source dropdown in the rules table already uses the rule spelling, so this matters mostly when you use the API.
A rule's Source list also offers a few names that have no control of their own, because they are switched on by another source. See Sources that only appear in rules.
socket: network data
socket marks data your program receives from another computer: anything read from a network connection, including the plain text read out of an encrypted (TLS) connection. It is the source most network services need.
On es-python, the data returned by the socket receive calls (recv, recvfrom, recv_into, recvmsg) and by the TLS socket read methods is marked.
- Switch on: the socket control, or
--make-socket-vuln/ES2_MAKE_SOCKET_VULN=1. - Origins you will see:
socket:fd,socket:connected,socket:unbound; TLS reads showssl:fd, and a memory-mapped socket showsmmap-socket:fd.
import socket
s = socket.create_connection(("example.com", 80))
s.sendall(b"GET / HTTP/1.0\r\n\r\n")
data = s.recv(4096) # untrusted, origin socket:connected
Limits. When socket is the only source switched on, data read with a raw os.read() on a socket is not marked; switching on any other source as well restores it. Older socket-only machines can use this source and no other.
file: data read from files
file marks the contents of files your program opens and reads from disk. Use it for programs that process uploaded, downloaded or user-supplied files.
On es-python, the bytes returned by open(path).read(), os.read(), os.pread() and memory-mapping a file are marked, when they come from a regular file.
- Switch on: the file control, or
--make-file-vuln/ES2_MAKE_FILE_VULN=1. - Origins you will see:
fd:posix_read,fd:posix_pread,fileio.read(candidate),fileio.readall(candidate); a memory-mapped file showsmmap-file:fd.
ES2_MAKE_FILE_VULN=1 es-python -c 'print(open("/etc/hostname").read())'
# the file contents are untrusted, origin fileio.readall(candidate)
Limits. Checking each read adds some cost to programs that read many files.
stdin: standard input
stdin marks data fed to your program on standard input: text piped into it or typed at the keyboard. Use it for command-line tools and filters that read their input this way.
On es-python, the string returned by input() and reads from sys.stdin are marked.
- Switch on: the stdin control, or
--make-stdin-vuln/ES2_MAKE_STDIN_VULN=1. - Origins you will see:
stdin:input(frominput()) andstdin(from reads onsys.stdin).
name = input("name? ") # untrusted, origin stdin:input
blob = sys.stdin.buffer.read() # untrusted, origin stdin
foreign: code other users can change
foreign reports when your program loads code from a file that another user on the machine can write to. An attacker who can edit such a file can run their own code inside your program. Unlike the other sources, it reports the loading itself, even while the program is starting, not a flow of data.
On es-python this covers importing a module, running the main script, and compile, exec or eval of a file's contents, whenever that file is writable by another user.
- Switch on: the foreign control, or
--make-foreign-vuln/ES2_MAKE_FOREIGN_VULN=1. It is not included in--make-everything-vuln. - Event you will see:
foreign-code:followed by the file's path, once per file per process, so an import loop does not flood you. Under a Raise rule the load is stopped. - Side effect: on es-python, switching foreign on also switches socket on, unless you set socket to Off.
- Shadowed modules: es-python also reports a standard-library module that was replaced by a file of the same name somewhere else (event
shadow import, rule sourceshadow-import).
To trust some locations on purpose, such as a shared tools directory your team maintains, list their absolute path prefixes in ES2_FOREIGN_CODE_ALLOW, separated by colons. Nothing is exempt automatically.
ES2_MAKE_FOREIGN_VULN=1 ES2_FOREIGN_CODE_ALLOW=/opt/team-tools:/srv/shared es-python app.py
# importing /tmp/evil.py (writable by another user) -> event foreign-code:/tmp/evil.py
env: environment variables
env marks the values of environment variables your program reads. Whoever starts a process controls its environment, so a value read from it is untrusted when something else builds that environment from user input.
On es-python, values read from os.environ or os.getenv() are marked, and so are the command-line arguments in sys.argv. Only the value is marked, never the variable name.
- Switch on: the env control, or
--make-env-vuln/ES2_MAKE_ENV_VULN=1. - Origins you will see:
envfor environment values andargvfor command-line arguments.
import os, sys
token = os.environ["API_TOKEN"] # untrusted, origin env
target = sys.argv[1] # untrusted, origin argv
argv: command-line arguments
argv marks the command-line arguments a program was started with. They are attacker-controlled when another program builds the command line from user input, and fully trusted when a person typed them.
On es-python, arguments are covered by the env source: switch on env to track them. The argv control has no effect on an es-python machine.
make_vuln: data you mark yourself
make_vuln lets your own code mark a value as untrusted, by calling the make_vuln() builtin on it. It is mainly a testing tool: mark a value, pass it through your code, and see which risky operations it reaches.
import os
payload = make_vuln("hello; id")
os.system("echo " + payload) # event: untrusted data reached os system
- Switch on: set the make_vuln control to On. There is no flag: while it is Unset or Off,
make_vuln()does nothing and returns its argument unchanged. - Custom numbers:
make_vuln(value, 42)tags the value with your own number, so a rule can target it. Choose Custom number… in a rule's Source field and enter the number. See Source.
weak_random: predictable random numbers
weak_random marks values from a random number generator that is not meant for security. Such values are fine for shuffling or jitter, but predictable to an attacker when used as a session token or a password reset link. The event is reported where the value is used, not where it was generated.
On es-python this covers random.getrandbits (which the rest of the random module uses) and uuid1. uuid4 is not included, because it uses the operating system's secure generator.
- Switch on:
--make-weakrandom-vulnorES2_MAKE_WEAKRANDOM_VULN=1, or--make-everything-vuln. The dashboard control does not reach es-python yet. - In a rule: spell it
weak-random(see Source names in rules).
hardcoded: credentials in source code
hardcoded finds credentials, API keys and encryption keys written directly into your source code, and reports where they are used. It is the one source with a steady cost even when it finds nothing, so leave it off unless you are looking for this.
- Switch on:
--make-secret-vulnorES2_MAKE_SECRET_VULN=1, or--make-everything-vuln. The dashboard control does not reach es-python yet.
db_rows: data read back from a database
db_rows marks values your program reads back out of a database. Data your own application stored is still attacker data: it is how stored cross-site scripting and second-order SQL injection reach a risky operation, long after the request that planted them.
It marks a lot of data on a busy application, which is why it is a source of its own rather than part of socket.
- Switch on: the db_rows control, or
--make-db-rows-vuln/ES2_MAKE_DB_ROWS_VULN=1, or--make-everything-vuln. - Covers on es-python: the built-in
sqlite3module and the PostgreSQL, MySQL, MariaDB and Oracle drivers that ship with es-python. - In a rule: spell it
db(see Source names in rules).
identity: database identity checks
identity is not a source of untrusted data. It is a switch for a check of your database queries: it compares the schema with the values bound to each query and reports a username stored in one form but looked up in another, or a check-then-insert race on a column with no unique constraint.
It is shown for es-python only, is off by default, and does not change what es-python reports today.
handoff: follow a flow into the browser
handoff is not a source either. When it is on, a monitored web service records which parts of each response came from that visitor's own input, so the EyalSec browser (es-chromium) can keep following that data in the page and report reflected and stored cross-site scripting.
It marks nothing, so no event is ever attributed to it and no rule can target it. It is shown for es-python, is off by default, and has no flag: only setting it to On switches it on. Turn it on only for a machine you are actively investigating, because it adds a little information to every response the service sends.
Turn every source on at once
es-python can switch on several sources for one run with a single flag: --make-everything-vuln, or ES2_MAKE_EVERYTHING_VULN=1. It covers socket, file, stdin, env, weak_random, hardcoded and db_rows.
It does not include foreign or make_vuln; switch those on separately. Like any flag, it only applies to sources whose dashboard control is Unset: a source set to Off stays off.
es-python --make-everything-vuln app.py
In the dashboard, the equivalent is Set all sources: All On (see Set all sources). It covers the Taint sources section only, not the switches under Other.
Sources that only appear in rules
A few names appear in a rule's Source list but have no control in Taint sources, because they are switched on by another source or by a flag. You can still target them with a rule.
| Rule source | Switched on by |
|---|---|
ssl-unverified |
socket (data from a TLS peer whose certificate was not checked) |
shadow-import |
foreign (es-python) |
fuzzer |
the fuzzing flags (es-python) |
db |
db_rows |