Bash

Useful commands/syntax and bash tricks

Description

Bash is the command line that almost all Linux machines use, or at least are built on top of. It has a lot of special syntaxes to customize how a command is run, and what the arguments are.

bash vs sh

bash is an improved version of sh, with a lot of features like command substitution, redirection and much more syntax.

Sometimes you may find yourself in an sh shell, and want to upgrade to bash. In an interactive shell, you can simply run /bin/bash, and otherwise use /bin/bash -c 'command'. In this string, you can put any bash syntax and make sure it is run with bash, instead of sh.

Chaining Commands

One very powerful feature of bash is its ability to chain multiple commands together.

You can simply execute multiple after each other commands in one line by separating them with a ; semicolon:

$ echo a; echo b
a
b

This becomes more powerful when introducing conditions. Programs all return an exit code, which is supposed to be 0 on success, and anything else on failure. Using the && syntax you can run the second command only if the first was successful.

$ base64 file.txt && echo "Success!"
SGVsbG8sIHdvcmxkIQo=
Success!
$ base64 wrong.txt && echo "Success!"
base64: wrong.txt: No such file or directory

The opposite of this is the || syntax. It only runs if the first command was unsuccessful.

$ base64 file.txt || echo "Error!"
SGVsbG8sIHdvcmxkIQo=
$ base64 wrong.txt || echo "Error!"
base64: wrong.txt: No such file or directory
Error!

The most powerful operator in bash is the | pipe operator. It allows you to chain commands together, by putting the first command's STDOUT into the second command's STDIN. Many tools support input from STDIN in some way to allow this chaining of commands:

$ seq 1 5 | sed s/3/THREE/
1
2
THREE
4
5
# # Some commands that expect a filename argument, accept '-' to read from STDIN
$ seq 1 10 | ffuf -u http://example.com/FUZZ -w -

Wildcards

Sometimes you want to perform some action on multiple files with a certain pattern. If you want to run a command on every file in a certain directory, for example, wildcards are a really useful option. Here is an example:

$ ls
first  second  third
$ file first second third
first:  empty
second: ASCII text
third:  PNG image data, 800 x 600, 8-bit/color RGBA, non-interlaced
$ file *
first:  empty
second: ASCII text
third:  PNG image data, 800 x 600, 8-bit/color RGBA, non-interlaced

A wildcard simply replaces the pattern with all files matching the pattern, separated by spaces. There are a few different characters that are wildcards, and have different functions:

  • *: Matches any text of any length (example: *.txt matches any file ending with ".txt")

  • ?: Matches any single character (example: ????.txt matches a 4-character .txt file)

  • []: Match any single character in a set (example: [a-n].txt matches any single letter file in the first half of the alphabet ending with ".txt")

Note 1: By default, these wildcard patterns will ignore any file starting with a . dot. These are so-called dotfiles that are meant to be more hidden.

Environment Variables

Environment variables are like temporary variables you can set to influence commands. You can then substitute references to these variables with their value, or let some program read the variable itself.

You can export a variable in the current session using the export command:

$ export SOMETHING="text"

Then you can refer to that variable later in a future command:

$ echo "$SOMETHING"
text
$ echo "before${SOMETHING}after"
beforetextafter
# # You can also use `env` to see all current variables
$ env | grep SOMETHING
SOMETHING=text

If you want to set a variable for a single command once, and not for the whole session, you can simply prepend the export syntax to the command:

$ SOMETHING="text" env | grep SOMETHING
SOMETHING=text

Command Substitution

There are a few different ways to execute commands, and put the output of those commands into arguments of another command. This is known as command substitution, as it is replaced with its output.

To simply replace an argument with the literal output of a command, you can surround it with `` backticks, or the equivalent $():

$ echo `seq 1 10`
1 2 3 4 5 6 7 8 9 10
$ echo $(seq 1 10)
1 2 3 4 5 6 7 8 9 10

Another less common syntax is using <(). This replaces the argument with a temporary path to the output. It is very useful for commands that expect a filename as an argument, like so:

$ echo <(seq 1 5) <(seq 6 10)
/dev/fd/63 /dev/fd/62
$ cat <(seq 1 5) <(seq 6 10)
1
2
3
4
5
6
7
8
9
10

Output Redirection

Using some special characters, you can pass the output of a command or file, to other commands or files. The most common is >, which writes the output of its left, to a file named on the right:

$ echo "Hello, world!" > file.txt
# # If no command is specified, an empty file is created
$ > new.txt

Warning: This will replace the file's contents if it already exists. To append data to the output file, you can use >>:

$ echo "new line" >> file.txt
$ cat file.txt
Hello, world
new line

Input Redirection (Advanced)

A less common way to read a file as STDIN to a command is by using <. It is equivalent to piping cat [file] | to the command.

$ ./program < input.txt

Another useful trick to specify a string on the command line, to pass as STDIN to a command is using <<<:

$ base64 <<< 'Hello, world!'
SGVsbG8sIHdvcmxkIQo=
$ echo 'Hello, world!' | base64  # Equivalent
SGVsbG8sIHdvcmxkIQo=

You can find all details you'll ever need in the manual here, which explains some interesting tricks:

One is that you can specify a specific file descriptor of where the file should be opened in the program. This may be useful in restricted binary exploitation scenarios, where you need to have a certain file or directory open at a specific file descriptor:

[n]<file
$ ./program 3< /path/to/file    # Open file at FD 3
$ ./program 3< /path/to/dir/    # Open directory at FD 3

A more generally useful piece of syntax is the &> characters, which redirect both STDOUT and STDERR together into a file:

$ ./program &> output.txt
# # === equivalent to ===
$ ./program >output.txt 2>&1

Escape Characters

Use echo -e to escape certain untypable characters, like Ctrl+C, or ANSI Escape Codes:

$ echo -e "\x03"  # Ctrl+C

Filter Bypass

If you are trying to bypass some command injection filter, there are a few lesser-known pieces of bash syntax that the author of the filter may not have thought of.

Try some Command Substitution, and here is another really weird trick to not require spaces:

$ {cat,/etc/passwd}

$ cat${IFS}/etc/passwd
# # If { or } not allowed:
$ cat$IFS/etc/passwd
# # If you require a normal character after the variable:
$ cat$IFS'file.txt'
$ cat$IFS"file.txt"

When letters aren't allowed, variables and wildcards are very powerful. This command for example translates to bash < [every file in the directory] which might error out some contents of the files if they are not valid bash:

${0/-/} <*

For some more advanced usage of these variable substitutions, see this reference

Last updated