1 Why do we use captcha?

If you’re a programmer who just wants to see the dry stuff, and don’t want to hear me talk crap, swipe right to the end here and get the code.

Describe a business scenario:

Suppose you wanted to waste XXX company’s SMS captcha, what would you do?

If you are a programmer in this company, how can you protect the company?

1.2 Hypothetical offensive and defensive battles

The first round

Attacker: (simulated interface call) using Postman tools, will send SMS interface crazy call.

Defense: (carrier protection) As the back-end team, immediately start meeting mode, thinking how to prevent? Okay, interface vendor’s interface protection is on. You can only request the same mobile phone number 10 times a day and once an hour.


The second round

Attacker: failed to call the interface. Abnormal state was enabled. The same mobile phone number was restricted. Good, using the mobile phone dictionary mode, as long as my mobile phone number is enough, it will certainly be able to keep calling you, wrote a script to simulate the call.

Defender: (IP address protection) Through the interface log, there is a person who calls the interface every second, and then sends it, without any user action? Through IP monitoring, it is found that the attack is from the same IP address, so the protection mode is enabled immediately and the same IP address is restricted.


The third round

Attacker: suddenly found that the computer interface call failed, but the call of the mobile phone hot spot was successful. Turn on virtual IP and start calling.

Defense: Call in the front end, let’s talk, captcha open.


2 Verification code scheme

As defenders, we need to understand that a task, while I’m collecting users, may be more interested in a person than a machine. So how do you know it’s a person?

3 Text verification code

  1. Random pictures of numbers or letters. This is the original and simplest captcha
  2. GIF format of random numbers or letters
  3. Random numbers + random capital letters + random interference pixel + random position BMP format picture
  4. Random English letters + random color + random position + random length of JPG format picture, the letter case, position, color, length and so on random display

(The above is an intermittent scheme…)

The end result, as you already know, is that there are libraries for both the front end and the back end.

And we designed it to look like this. So is this a good idea? After years of upgrading obscure text recognition technology, such captchas have become less secure.

4 Verification code

Upgrade the text verification code, slightly interactive form, geography questions (as shown), math questions, common sense questions. Then you will find that there are always a few questions, really people do not have the answer.

5 Image verification code

Put some pictures and let you find the following books, flowers, traffic lights. This is a classic verification code, the most famous is now 12306 anti-brush ticket measures, for the user, there are always a few face blind patients, and look at the eye of the time. While protecting the interface, it also annoys the user.

6 Slide the verification code

  1. Drag the image to the bottom
  2. Compound the picture with the gap
  3. Open 2 notches on the picture

When the requirements for the user are reduced, the user experience is greatly upgraded. As the saying goes, it is not difficult to use more people. Even though the second generation has been upgraded by two notches, a sliding verification tool using Puppeteer to crack the polar is also born.

7 Baidu Verification Code (with code)

This scheme, which I found in the cracking scheme of PY, you can think of it as generating a zillions of models and finding a similar algorithm to crack the image. So let’s say that an image will have 360 angles, we want to remove the degrees that we don’t need to rotate, say 340 angles, remove the angles that don’t rotate much, 180 angles, for cracking, that’s 260 images. So let’s say I have 1,000 images in my library, and you need to hack, and you need 180,000 images to store, and I can choose to update those images regularly.

7.1 the node code

  1. Images must definitely be generated at the back end. => Generate a picture
  2. You don’t want images stored on the server (temporary images, after all) => Base64 is a better solution => Sharp library
  3. Actually the most meaningful is the 90-270 random number (maybe more, no specific test)=> random number
  4. Here we choose KOA for our service

Here the random number is stored, if you want to use in the project, need to store into the cache, here recommended redis.

const sharp = require('sharp')
const Koa = require('koa');
const router = require('koa-router') ()const img_name = './images/1.jpg' // Place an image. In the future, you can place a random image here
const max = 90; / / Max
const min = 270; / / the minimum
const app = new Koa();
let random = 0 // Just do demo, future storage redis is not needed here

app.use(router.routes())
router.get('/'.async (ctx) => {
    ctx.body = "hello!"
})

// Initiate the image request
router.get('/getImg'.async (ctx) => {
    // Generate a random number
  	random = Math.ceil(Math.random() * (max - min) + min);
  	// Create an image. Here you can view sharp's document in detail. For example, blur can be enabled.
    await sharp(img_name)
        .resize(400.400)
        .rotate(random)
        .toBuffer()
        .then(bitmap= > {
      			// 
            const base64str = Buffer.from(bitmap, 'binary').toString('base64'); / / base64 encoding
            ctx.body = {
                base64str: `data:image/png; base64,${base64str}`
                // id: this can be given a UUID to facilitate query}})})// Image validation
router.get('/validation'.async (ctx) => {
    const rotate = ctx.request.query.rotate;
    console.log(Math.abs(360 - rotate - random));
  	// Here 10 can be made bigger or smaller to adjust the difficulty
    if (Math.abs(360 - rotate - random) <= 10) {
        ctx.body = {
            flag: true
        }
        return;
    }
    ctx.body = {
        flag: false
    }
})


app.listen(3000.() = > {
    console.log('server is running at http://localhost:3000')});Copy the code

7.2 the vue code

Develop dragVerify.vue based on vue-drag-verify

<template>
  <div class="drag-verify-container">
    <div :style="dragVerifyImgStyle">
      <img
        ref="checkImg"
        :src="imgsrc"
        class="check-img"
        :class="{ goOrigin: isOk }"
        @load="checkimgLoaded"
        :style="imgStyle"
        alt=""
      />
      <div class="tips success" v-if="showTips && isPassing">{{ successTip }}</div>
      <div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{ failTip }}</div>
    </div>
    <div
      ref="dragVerify"
      class="drag_verify"
      :style="dragVerifyStyle"
      @mousemove="dragMoving"
      @mouseup="dragFinish"
      @mouseleave="dragFinish"
      @touchmove="dragMoving"
      @touchend="dragFinish"
    >
      <div
        class="dv_progress_bar"
        :class="{ goFirst2: isOk }"
        ref="progressBar"
        :style="progressBarStyle"
      >
        {{ successMessage }}
      </div>
      <div class="dv_text" :style="textStyle" ref="message">
        {{ message }}
      </div>

      <div
        class="dv_handler dv_handler_bg"
        :class="{ goFirst: isOk }"
        @mousedown="dragStart"
        @touchstart="dragStart"
        ref="handler"
        :style="handlerStyle"
      >
        <i :class="handlerIcon"></i>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: 'DragVerifyImg',
  props: {
    isPassing: {
      type: Boolean,
      default: false
    },
    width: {
      type: Number,
      default: 250
    },
    height: {
      type: Number,
      default: 40
    },
    text: {
      type: String,
      default: 'swiping to the right side'
    },
    successText: {
      type: String,
      default: 'success'
    },
    background: {
      type: String,
      default: '#eee'
    },
    progressBarBg: {
      type: String,
      default: '#76c61d'
    },
    completedBg: {
      type: String,
      default: '#76c61d'
    },
    circle: {
      type: Boolean,
      default: false
    },
    radius: {
      type: String,
      default: '4px'
    },
    handlerIcon: {
      type: String
    },
    successIcon: {
      type: String
    },
    handlerBg: {
      type: String,
      default: '#fff'
    },
    textSize: {
      type: String,
      default: '14px'
    },
    textColor: {
      type: String,
      default: '#333'
    },
    imgsrc: {
      type: String
    },
    showTips: {
      type: Boolean,
      default: true
    },
    successTip: {
      type: String,
      default: '验证通过'
    },
    failTip: {
      type: String,
      default: '验证失败'
    },
    minDegree: {
      type: Number,
      default: 90
    },
    maxDegree: {
      type: Number,
      default: 270
    }
  },
  mounted: function () {
    const dragEl = this.$refs.dragVerify;
    dragEl.style.setProperty('--textColor', this.textColor);
    dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px');
    dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px');
  },
  computed: {
    handlerStyle: function () {
      return {
        width: this.height + 'px',
        height: this.height + 'px',
        background: this.handlerBg
      };
    },
    message: function () {
      return this.isPassing ? '' : this.text;
    },
    successMessage: function () {
      return this.isPassing ? this.successText : '';
    },
    dragVerifyStyle: function () {
      return {
        width: this.width + 'px',
        height: this.height + 'px',
        lineHeight: this.height + 'px',
        background: this.background,
        borderRadius: this.circle ? this.height / 2 + 'px' : this.radius
      };
    },
    dragVerifyImgStyle: function () {
      return {
        'width': this.width + 'px',
        'height': this.width + 'px',
        'position': 'relative',
        'overflow': 'hidden',
        'border-radius': '50%'
      };
    },
    progressBarStyle: function () {
      return {
        background: this.progressBarBg,
        height: this.height + 'px',
        borderRadius: this.circle
          ? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px'
          : this.radius
      };
    },
    textStyle: function () {
      return {
        height: this.height + 'px',
        width: this.width + 'px',
        fontSize: this.textSize
      };
    },
    factor: function () {
      //避免指定旋转角度时一直拖动到最右侧才验证通过
      if (this.minDegree == this.maxDegree) {
        return Math.floor(1 + Math.random() * 6) / 10 + 1;
      }
      return 1;
    }
  },
  data() {
    return {
      isMoving: false,
      x: 0,
      isOk: false,
      showBar: false,
      showErrorTip: false,
      ranRotate: 0,
      cRotate: 0,
      imgStyle: {}
    };
  },
  methods: {
    checkimgLoaded: function () {
      this.ranRotate = 120;
    },
    dragStart: function (e) {
      if (!this.isPassing) {
        this.isMoving = true;
        this.x = e.pageX || e.touches[0].pageX;
      }
      this.showBar = true;
      this.showErrorTip = false;
      this.$emit('handlerMove');
    },
    dragMoving: function (e) {
      if (this.isMoving && !this.isPassing) {
        var _x = (e.pageX || e.touches[0].pageX) - this.x;
        var handler = this.$refs.handler;
        handler.style.left = _x + 'px';
        this.$refs.progressBar.style.width = _x + this.height / 2 + 'px';
        var cRotate = Math.ceil((_x / (this.width - this.height)) * this.maxDegree * this.factor);
        this.cRotate = cRotate;
        var rotate = cRotate;
        this.imgStyle = {
          transform: `rotateZ(${rotate}deg)`
        };
      }
    },
    dragFinish: function () {
      if (this.isMoving && !this.isPassing) {
        this.$emit('postRotate', this.cRotate);
        this.isMoving = false;
      }
    },
    setFinish(val) {
      if (val) {
        this.passVerify();
        return;
      }
      this.isOk = true;
      this.imgStyle = {
        transform: `rotateZ(${this.ranRotate}deg)`
      };
      const that = this;
      setTimeout(function () {
        const handler = that.$refs.handler;
        const progressBar = that.$refs.progressBar;
        handler.style.left = '0';
        progressBar.style.width = '0';
        that.isOk = false;
      }, 500);
      this.showErrorTip = true;
      this.$emit('update:isPassing', false);
      this.$emit('passfail');
    },
    passVerify: function () {
      this.$emit('update:isPassing', true);
      this.isMoving = false;
      var handler = this.$refs.handler;
      handler.children[0].className = this.successIcon;
      this.$refs.progressBar.style.background = this.completedBg;
      this.$refs.message.style['-webkit-text-fill-color'] = 'unset';
      this.$refs.message.style.animation = 'slidetounlock2 3s infinite';
      this.$refs.progressBar.style.color = '#fff';
      this.$refs.progressBar.style.fontSize = this.textSize;
      this.$emit('passcallback');
    },
    reset: function () {
      this.reImg();
      this.checkimgLoaded();
    },
    reImg: function () {
      this.$emit('update:isPassing', false);
      const oriData = this.$options.data();
      for (const key in oriData) {
        if (Object.prototype.hasOwnProperty.call(oriData, key)) {
          this[key] = oriData[key];
        }
      }
      var handler = this.$refs.handler;
      var message = this.$refs.message;
      handler.style.left = '0';
      this.$refs.progressBar.style.width = '0';
      handler.children[0].className = this.handlerIcon;
      message.style['-webkit-text-fill-color'] = 'transparent';
      message.style.animation = 'slidetounlock 3s infinite';
      message.style.color = this.background;
    },
    refreshimg: function () {
      this.$emit('refresh');
    }
  },
  watch: {
    imgsrc: {
      immediate: false,
      handler: function () {
        this.reImg();
      }
    }
  }
};
</script>
<style scoped>
.drag_verify {
  position: relative;
  background-color: #e8e8e8;
  text-align: center;
  overflow: hidden;
}
.drag_verify .dv_handler {
  position: absolute;
  top: 0px;
  left: 0px;
  cursor: move;
}
.drag_verify .dv_handler i {
  color: #666;
  padding-left: 0;
  font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
  color: #6c6;
  margin-top: 9px;
}
.drag_verify .dv_progress_bar {
  position: absolute;
  height: 34px;
  width: 0px;
}
.drag_verify .dv_text {
  position: absolute;
  top: 0px;
  color: transparent;
  -moz-user-select: none;
  -webkit-user-select: none;
  user-select: none;
  -o-user-select: none;
  -ms-user-select: none;
  background: -webkit-gradient(
    linear,
    left top,
    right top,
    color-stop(0, var(--textColor)),
    color-stop(0.4, var(--textColor)),
    color-stop(0.5, #fff),
    color-stop(0.6, var(--textColor)),
    color-stop(1, var(--textColor))
  );
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  -webkit-text-size-adjust: none;
  animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
  -webkit-text-fill-color: var(--textColor);
}
.goFirst {
  left: 0px !important;
  transition: left 0.5s;
}
.goOrigin {
  transition: transform 0.5s;
}
.goKeep {
  transition: left 0.2s;
}
.goFirst2 {
  width: 0px !important;
  transition: width 0.5s;
}
.drag-verify-container {
  position: relative;
  line-height: 0;
  border-radius: 50%;
}
.move-bar {
  position: absolute;
  z-index: 100;
}
.clip-bar {
  position: absolute;
  background: rgba(255, 255, 255, 0.8);
}
.refresh {
  position: absolute;
  right: 5px;
  top: 5px;
  cursor: pointer;
  font-size: 20px;
  z-index: 200;
}
.tips {
  position: absolute;
  bottom: 25px;
  height: 20px;
  line-height: 20px;
  text-align: center;
  width: 100%;
  font-size: 12px;
  z-index: 200;
}
.tips.success {
  background: rgba(255, 255, 255, 0.6);
  color: green;
}
.tips.danger {
  background: rgba(0, 0, 0, 0.6);
  color: yellow;
}
.check-img {
  width: 140%;
  margin-left: -20%;
  margin-top: -20%;
  border-radius: 50%;
  /* width: 100%;
*/
}
</style>
<style>
@-webkit-keyframes slidetounlock {
  0% {
    background-position: var(--pwidth) 0;
  }
  100% {
    background-position: var(--width) 0;
  }
}
@-webkit-keyframes slidetounlock2 {
  0% {
    background-position: var(--pwidth) 0;
  }
  100% {
    background-position: var(--pwidth) 0;
  }
}
</style>
Copy the code

app.vue

<template> <div id="app"> <drag-verify-img ref="verify" :imgsrc="imgsrc" :isPassing. Sync ="isPassing" text=" Please hold the slider and drag." SuccessText =" handlerIcon="el-icon-d-arrow-right" successIcon=" el-icon-circl-check "@postrotate ="postRotate" @passcallback="passcallback" @passfail="passfail" > </drag-verify-img> </div> </template> <script> import DragVerifyImg from './components/DragVerify.vue'; import axios from 'axios'; export default { name: 'App', components: { DragVerifyImg }, data: function () { return { imgsrc: '', isPassing: false }; }, created() { this.getImg(); }, methods: { async getImg() { try { const res = await axios.get('/api/getImg'); this.imgsrc = res.data.base64str; } catch (error) { console.log(error); } }, async postRotate(val) { const res = await axios.get(`/api/validation? rotate=${val}`); this.$refs.verify.setFinish(res.data.flag); }, passcallback() {console.log(' success '); }, passfail() { this.$refs.verify.reset(); // Default reset console.log(' failed '); }}}; </script> <style lang="scss"> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>Copy the code

8 lot address

Github.com/MYQ1996/dra…