Как предотвратить отображение личной информации в сетевом запросе через ответ xhr fetch

#mysql #node.js #reactjs #security

#mysql #node.js #reactjs #Безопасность

Вопрос:

Я создаю простой модуль планирования, в котором я позволю любому зайти на веб-сайт, чтобы назначить встречу через планировщик.

В модуле планировщика у меня есть следующее:

  1. Встречи назначаются с шагом в 30 минут, поэтому, например, если встреча назначена на 9:00, она будет выполняться с 9:00 до 9:30.
  2. Я сохраняю личную информацию о встрече, которая была запланирована на основе пользовательского ввода, такую как номер телефона, адрес электронной почты, полное имя
  3. После того, как пользователь отправляет расписание, я запускаю оператор SQL select, в котором он извлекает все данные из запланированных встреч из базы данных MYSQL в компонент планировщика, чтобы пользователи могли видеть, какие встречи уже назначены и какие слоты доступны в указанное время.

Это отлично работает и все такое, но я немного обеспокоен, когда я запускаю это в производство, я заметил, что в моих сетевых запросах я вижу все данные, которые были извлечены из БД. Может ли это быть проблемой безопасности, позволяющей любому пользователю переходить к сетевым запросам для просмотра извлеченных данных, содержащих личную информацию?

Итак, мой вопрос в том, как мне это предотвратить?

Вот мой код:

ReactJS

 import React, { Component } from 'react';

import appController from '../../controllers/appController';

import Scheduleservice from '../../services/Scheduleservice';
import { withRouter } from "react-router-dom";
import { Helmet } from 'react-helmet';
import Moment from 'moment';
import AOS from 'aos'; // Animate on scroll

import {
  ScheduleComponent,
  ResourcesDirective,
  ResourceDirective,
  ViewsDirective,
  ViewDirective,
  Inject,
  TimelineViews,
  Resize,
  DragAndDrop,
  TimelineMonth,
  Day,
  Week,
  WorkWeek,
  Month,
  Agenda
} from '@syncfusion/ej2-react-schedule';

import { DateTimePickerComponent } from '@syncfusion/ej2-react-calendars';
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import { DataManager, WebApiAdaptor, ODataV4Adaptor } from '@syncfusion/ej2-data';


//import * as dataSource from './datasource.json';

class Voipcall extends Component {
  constructor(props) {
    super(props);
    this.dataManager = new DataManager({
      url: '/api/selectSchedule',
      adaptor: new WebApiAdaptor,
    })
    this.state = {
      scheduleData: [],
      employeeData: [
        {
          Text: 'Facility Appointment',
          Id: 1,
          GroupId: 1,
          Color: '#9e5fff',
          Designation: 'Schedule an appointment',
        },
      ],
    };
  }

  onActionBegin(args) {
    //console.log('data sent after insertion', args);

    if(args.requestType === 'eventCreate' || args.requestType === 'eventChange') {
      let data = args.data instanceof Array ? args.data[0] : args.data;

      //console.log('Original Data', data);

      // If Subject is blank
      if(data.Subject !== 'Add title') {
        //console.log('subject is blank')

        // Format the data Role Id is guest which is 3
        let insertData = {
          RoleId: undefined ? "3" : "3",
          Subject: data.Subject === undefined ? null : data.Subject,
          FullName: data.fullName === undefined ? null : data.fullName,
          Resident: data.resident === undefined ? null : data.resident,
          CommunicationInfo: data.communicationInfo === undefined ? null : data.communicationInfo,
          CommunicationMethod: data.communicationMethod === undefined ? null : data.communicationMethod,
          Email: data.email === undefined ? null : data.email,
          Phone: data.phone === undefined ? null : data.phone,
          StartTime: data.StartTime === undefined ? null : appController.converttoISO(data.StartTime),
          EndTime: data.EndTime === undefined ? null : appController.converttoISO(data.EndTime),
          Description: data.Description === undefined ? null : data.Description
        }

      console.log('data insertion', insertData)

      // Insert Schedule Data into DB
      const insertSchedule = Scheduleservice.insertSchedule(insertData);


      // Refresh the scheduler
      this.props.history.push({
        pathname: '/voipCall',
        });
      }
    }
}

onEventRendered(args) {

  console.log("Start time ", Moment(args.data.startTime).format());
  console.log("End time ", Moment(args.data.endTime).format());

  if(args.element.innerText !== 'Not Available') {
    args.element.style.backgroundColor = '#0CA6C4';
    args.element.innerText = 'Appointment Booked';
  }

  if(args.element.innerText === 'Not Available') {
    args.element.style.backgroundColor = '#ccc';
  }

  if(args.element.innerText === 'Appointment Booked') {
    args.element.style.backgroundColor = '#09A6CA';
  }
}

  getEmployeeName(value) {
    return value.resourceData[value.resource.textField];
  }
  getEmployeeImage(value) {
    let resourceName = this.getEmployeeName(value);
    return resourceName.toLowerCase();
  }
  getEmployeeDesignation(value) {
    return value.resourceData.Designation;
  }
  resourceHeaderTemplate(props) {
    return (
      <div className='template-wrap'>
        <div className='employee-category'>
          <div
            className={'employee-image '   this.getEmployeeImage(props)}
          ></div>
          <div className='employee-name'>{this.getEmployeeName(props)}</div>
          <div className='employee-designation'>
            {this.getEmployeeDesignation(props)}
          </div>
        </div>
      </div>
    );
  }

  editorTemplate(props) {
    return props !== undefined ? (
      <table
        className='custom-event-editor'
        style={{ width: '100%', cellpadding: '5' }}
      >
        <tbody>
          <tr>
            <td className='e-textlabel'>Meeting Name</td>
            <td style={{ colspan: '4' }}>
              <input
                id='Summary'
                className='e-field e-input'
                type='text'
                name='Subject'
                style={{ width: '100%' }}
                placeholder='Event Name...'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Full Name</td>
            <td style={{ colspan: '4' }}>
              <input
                id='fullName'
                className='e-field e-input'
                type='text'
                name='fullName'
                style={{ width: '100%' }}
                placeholder='Full Name'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Resident Requesting to Speak to</td>
            <td style={{ colspan: '4' }}>
              <input
                id='resident'
                className='e-field e-input'
                type='text'
                name='resident'
                style={{ width: '100%' }}
                placeholder='Resident you wish to speak to...'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Preferred Method of Communication</td>
            <td style={{ colspan: '4' }}>
              <DropDownListComponent
                id='communicationMethod'
                placeholder='Choose Method of Communication'
                data-name='communicationMethod'
                className='e-field'
                style={{ width: '100%' }}
                dataSource={['Zoom', 'Skype', 'Facetime', 'Viber']}
              ></DropDownListComponent>
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Communication Information</td>
            <td style={{ colspan: '4' }}>
              <input
                id='communicationInfo'
                className='e-field e-input'
                type='text'
                name='communicationInfo'
                style={{ width: '100%' }}
                placeholder='Communication ID'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Email Address</td>
            <td style={{ colspan: '4' }}>
              <input
                id='email'
                className='e-field e-input'
                type='email'
                name='email'
                style={{ width: '100%' }}
                placeholder='Email Address'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Phone Number ex: 123456789</td>
            <td style={{ colspan: '4' }}>
              <input
                id='phone'
                className='e-field e-input'
                type='tel'
                name='phone'
                style={{ width: '100%' }}
                placeholder='Phone Number'
              />
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>From</td>
            <td style={{ colspan: '4' }}>
              <DateTimePickerComponent
                id='StartTime'
                format='MM/dd/yy hh:mm a'
                data-name='StartTime'
                value={new Date(props.startTime || props.StartTime)}
                className='e-field'
                disabled
              ></DateTimePickerComponent>
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>To</td>
            <td style={{ colspan: '4' }}>
              <DateTimePickerComponent
                id='EndTime'
                format='MM/dd/yy hh:mm a'
                data-name='EndTime'
                value={new Date(props.endTime || props.EndTime)}
                className='e-field'
                disabled
              ></DateTimePickerComponent>
            </td>
          </tr>
          <tr>
            <td className='e-textlabel'>Reason</td>
            <td style={{ colspan: '4' }}>
              <textarea
                id='Description'
                className='e-field e-input'
                name='Description'
                rows={3}
                cols={50}
                style={{
                  width: '100%',
                  height: '60px !important',
                  resize: 'vertical',
                }}
              ></textarea>
            </td>
          </tr>
        </tbody>
      </table>
    ) : (
      <div></div>
    );
  }

  fetchSchedule = async () => {
    const selectSchedule = await Scheduleservice.selectSchedule();

    await this.setState({
      scheduleData: selectSchedule
    })

    //console.log("Schedule Data State is ", this.state.scheduleData);
    //console.log("Local Datasource is ", dataSource.scheduleData);
    //console.log("state is ", this.state)

  }

  componentDidMount = async () => {
    await this.fetchSchedule();
  }

  render() {
    const { match, location, history } = this.props;
    return (
      <div className='voipContainer'>
        <div className='fluid-container'>
          <div className='container'>
            <div className='row'>
              <div className='schedule-control-section'>
                <div className='col-lg-12 control-section'>
                  <div className='control-wrapper drag-sample-wrapper'>
                    <div className='schedule-container'>
                      <ScheduleComponent
                        //ref={(schedule) => (this.scheduleObj = schedule)}
                        //ref={(schedule) => console.log("Schedule Data is", schedule)}
                        cssClass='block-events'
                        width='100%'
                        height='650px'
                        selectedDate={new Date()}
                        minDate={appController.previousDay(new Date())}
                        showTimeIndicator={false}
                        currentView='Day'
                        resourceHeaderTemplate={this.resourceHeaderTemplate.bind(
                          this
                        )}
                        actionBegin={this.onActionBegin.bind(this)}
                        eventSettings={{
                          dataSource: this.dataManager,
                        }}
                        eventRendered={this.onEventRendered.bind(this)}
                        // group={{
                        //   enableCompactView: false,
                        //   resources: ['Employee'],
                        // }}
                        //readonly={true}
                        timeScale={{ enable: true, interval: 30, slotCount: 1 }}
                        editorTemplate={this.editorTemplate.bind(this)}
                      >
                        <ResourcesDirective>
                          <ResourceDirective
                            field='EmployeeId'
                            title='Employees'
                            name='Employee'
                            allowMultiple={true}
                            dataSource={this.state.employeeData}
                            textField='Text'
                            idField='Id'
                            colorField='Color'
                          ></ResourceDirective>
                        </ResourcesDirective>
                        <ViewsDirective>
                          <ViewDirective option='Day' />
                          <ViewDirective option='TimelineDay' />
                        </ViewsDirective>
                        <Inject
                          services={[
                            Day, Week, WorkWeek, Month, Agenda
                          ]}
                        />
                      </ScheduleComponent>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }
}

export default withRouter(Voipcall);  

Инструкция MYSQL

 var db = require('../dbconnection');

var schedule = {
  selectSchedule: function (data, callback) {
    db.query(
      'select Id, Role_Id, Subject, Location, StartTime, EndTime, Description, Owner, Priority, Recurrence, RecurrenceType, RecurrenceTypeCount, Reminder, Categorize, CustomStyle, AllDay, RecurrenceStartDate, RecurrenceEndDate, RecurrenceRule, StartTimeZone, EndTimeZone, IsBlock, isSlotAvailable, FullName, Resident, CommunicationMethod, CommunicationInfo, Email, Phone, Reason from schedule',
      callback
    );
  },
  insertSchedule: function(data, callback) {
    db.query('insert schedule set Role_Id=?, Subject=?, FullName=?, Resident=?, CommunicationMethod=?, CommunicationInfo=?, Email=?, Phone=?, Reason=?, IsBlock=?, StartTime=?, EndTime=?', [data.RoleId, data.Subject, data.FullName, data.Resident, data.CommunicationMethod, data.CommunicationInfo, data.Email, data.Phone, data.Description, "true", data.StartTime, data.EndTime], callback)

    //console.log('data got sent to backend', data);
  }
};

module.exports = schedule;  

Скриншот сетевого запроса вместе с ответом в формате JSON:

введите описание изображения здесь

 [
   {
      "Id":1,
      "Role_Id":null,
      "Subject":"Bering Sea Gold DB Test",
      "Location":"chn",
      "StartTime":"2020-09-23T09:00:00",
      "EndTime":"2020-09-23T10:30:00",
      "Description":"",
      "Owner":1,
      "Priority":null,
      "Recurrence":1,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":"1,2",
      "CustomStyle":null,
      "AllDay":"false",
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":"FREQ=DAILY;INTERVAL=2;COUNT=10",
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"",
      "isSlotAvailable":null,
      "FullName":null,
      "Resident":null,
      "CommunicationMethod":null,
      "CommunicationInfo":null,
      "Email":null,
      "Phone":null,
      "Reason":null
   },
   {
      "Id":2,
      "Role_Id":null,
      "Subject":"Not Available",
      "Location":null,
      "StartTime":"2020-08-01T03:00:00.000Z",
      "EndTime":"2020-08-01T09:00:00.000Z",
      "Description":null,
      "Owner":null,
      "Priority":null,
      "Recurrence":0,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":null,
      "CustomStyle":null,
      "AllDay":null,
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":"FREQ=DAILY;INTERVAL=1;",
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"true",
      "isSlotAvailable":null,
      "FullName":null,
      "Resident":null,
      "CommunicationMethod":null,
      "CommunicationInfo":null,
      "Email":null,
      "Phone":null,
      "Reason":null
   },
   {
      "Id":22,
      "Role_Id":3,
      "Subject":"Test Appointment 1",
      "Location":null,
      "StartTime":"2020-09-24T14:30:00.000Z",
      "EndTime":"2020-09-24T15:00:00.000Z",
      "Description":null,
      "Owner":null,
      "Priority":null,
      "Recurrence":0,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":null,
      "CustomStyle":null,
      "AllDay":null,
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":null,
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"true",
      "isSlotAvailable":null,
      "FullName":"John Doe",
      "Resident":"Alex West",
      "CommunicationMethod":"Facetime",
      "CommunicationInfo":"test@test.com",
      "Email":"test@test.com",
      "Phone":"999999999",
      "Reason":"This is a test Module."
   },
   {
      "Id":23,
      "Role_Id":3,
      "Subject":"Test Appointment 2",
      "Location":null,
      "StartTime":"2020-09-24T18:30:00.000Z",
      "EndTime":"2020-09-24T19:00:00.000Z",
      "Description":null,
      "Owner":null,
      "Priority":null,
      "Recurrence":0,
      "RecurrenceType":null,
      "RecurrenceTypeCount":null,
      "Reminder":null,
      "Categorize":null,
      "CustomStyle":null,
      "AllDay":null,
      "RecurrenceStartDate":null,
      "RecurrenceEndDate":null,
      "RecurrenceRule":null,
      "StartTimeZone":null,
      "EndTimeZone":null,
      "IsBlock":"true",
      "isSlotAvailable":null,
      "FullName":"Jason Stride",
      "Resident":"Chris Thomson",
      "CommunicationMethod":"Facetime",
      "CommunicationInfo":"test2@test.com",
      "Email":"test2@test.com",
      "Phone":"999999999",
      "Reason":"This is test appointment 2"
   }
]  

Ответ №1:

Три вещи, которые следует иметь в виду:

  1. используйте https: // для доставки данных в браузеры ваших пользователей. Киберпреступникам очень сложно перехватить данные, доставленные таким образом.

  2. убедитесь, что каждый пользователь получает только свои собственные данные или данные, которые он имеет право просматривать. Не полагайтесь на свой интерфейсный код, чтобы скрыть данные пользователя A от пользователя B. Это не сработает, потому что любой пользователь может просмотреть ваши запросы и ответы на вкладке Сеть в devtools. Обычно вы делаете это с помощью трудно угадываемого токена сеанса, привязанного к каждому зарегистрированному пользователю.

  3. Не позволяйте вашему веб-серверу возвращать какие-либо данные, которые вы не хотите, чтобы пользователь браузера видел.

Комментарии:

1. Спасибо за совет. В рабочей среде я использую https, но я думаю, что в конечном итоге я буду делать только выбор идентификатора из моего бэкэнда и отображение его всем пользователям для просмотра, а не личную информацию, которая все равно должна отображаться в планировщике. Это нормально делать?

2. На самом деле я понял. Я просто изменил свой оператор select на своем сервере, чтобы выбрать только идентификатор, который будет отображаться в планировщике, и удалил все операторы select, содержащие личную информацию для других пользователей.

3. Да, именно так. Всегда знайте идентификатор пользователя и всегда используйте WHERE ID = ? для идентификатора, который вы знаете, когда вы передаете конечные точки REST для доставки данных в ваше приложение.